diff options
163 files changed, 2847 insertions, 1519 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk index 02e8eecbb721..e6801034cd97 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -262,6 +262,7 @@ $(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/li $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin) +$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/Ravenwood.bp b/Ravenwood.bp index 255ec924abee..74b34fbcf2a1 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -246,12 +246,20 @@ java_genrule { visibility: ["//visibility:private"], } +java_genrule { + name: "z00-all-updatable-modules-system-stubs", + cmd: "cp $(in) $(out)", + srcs: [":all-updatable-modules-system-stubs"], + out: ["z00-all-updatable-modules-system-stubs.jar"], + visibility: ["//visibility:private"], +} + android_ravenwood_libgroup { name: "ravenwood-runtime", libs: [ "100-framework-minus-apex.ravenwood", "200-kxml2-android", - "all-updatable-modules-system-stubs", + "android.test.mock.ravenwood", "ravenwood-helper-runtime", "hoststubgen-helper-runtime.ravenwood", @@ -267,6 +275,9 @@ android_ravenwood_libgroup { "ravenwood-junit-impl-flag", "mockito-ravenwood-prebuilt", "inline-mockito-ravenwood-prebuilt", + + // It's a stub, so it should be towards the end. + "z00-all-updatable-modules-system-stubs", ], jni_libs: [ "libandroid_runtime", diff --git a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py index eea3b84a4498..373355a1bbf1 100755 --- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py +++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py @@ -61,8 +61,8 @@ print("package android.libcore;") imports = """ import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; -import android.test.suitebuilder.annotation.LargeTest; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; @@ -118,4 +118,4 @@ for i in range(max_conflict_depth): print(" default void f{}() {{}}".format(i*imt_size + j)) print(" }") -print("}")
\ No newline at end of file +print("}") diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py index f3a1fff52205..01abdb6c4be1 100755 --- a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py +++ b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py @@ -160,8 +160,8 @@ package android.libcore.varhandles; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; -import android.test.suitebuilder.annotation.LargeTest; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 852b00b38347..d5a58d11ac01 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -1771,7 +1771,13 @@ public final class FlexibilityController extends StateController { final int logicalIndex = mapping.getLogicalSlotIndex(); if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) { // Callback already exists. No need to create a new one or remove it. - callbacksToRemove.remove(logicalIndex); + for (int i = callbacksToRemove.size() - 1; i >= 0; i--) { + if (callbacksToRemove.get(i) == logicalIndex) { + callbacksToRemove.remove(i); + break; + } + } + continue; } final LogicalIndexCarrierPrivilegesCallback callback = diff --git a/core/api/system-current.txt b/core/api/system-current.txt index eabe1f1c271a..ba1bcc8852c9 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10342,7 +10342,7 @@ package android.net.wifi.sharedconnectivity.app { method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getModelName(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR; field public static final int DEVICE_TYPE_AUTO = 5; // 0x5 @@ -10356,7 +10356,7 @@ package android.net.wifi.sharedconnectivity.app { public static final class NetworkProviderInfo.Builder { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e225c5b03f8b..211fc961196c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1774,16 +1774,16 @@ package android.hardware.input { } public final class InputManager { - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings(); method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors(); method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping(); method public int getMousePointerSpeed(); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int); - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index c4fe06170414..6cc71e5450ae 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -975,14 +975,14 @@ RequiresPermission: android.hardware.hdmi.HdmiControlManager#getHdmiCecVersion() Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int): Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String): - Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String): Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String): - Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String): Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback): Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes(): @@ -2035,6 +2035,10 @@ UnflaggedApi: android.content.pm.UserInfo#isPrivateProfile(): New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile() UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary(): New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary() +UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String) +UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String) UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>): New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>) UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest(): diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index caaaf519eaca..d4812dd612c3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -50,6 +50,7 @@ import android.app.RemoteServiceException.BadUserInitiatedJobNotificationExcepti import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.RemoteServiceException.CrashedByAdbException; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException; import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; @@ -2236,6 +2237,9 @@ public final class ActivityThread extends ClientTransactionHandler case ForegroundServiceDidNotStartInTimeException.TYPE_ID: throw generateForegroundServiceDidNotStartInTimeException(message, extras); + case ForegroundServiceDidNotStopInTimeException.TYPE_ID: + throw generateForegroundServiceDidNotStopInTimeException(message, extras); + case CannotPostForegroundServiceNotificationException.TYPE_ID: throw new CannotPostForegroundServiceNotificationException(message); @@ -2266,6 +2270,15 @@ public final class ActivityThread extends ClientTransactionHandler throw new ForegroundServiceDidNotStartInTimeException(message, inner); } + private ForegroundServiceDidNotStopInTimeException + generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStopInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index c5ad1105397e..c624c43405a3 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -71,6 +71,33 @@ public class RemoteServiceException extends AndroidRuntimeException { } /** + * Exception used to crash an app process when it didn't stop after hitting its time limit. + * + * @hide + */ + public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 7; + + private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname"; + + public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) { + super(msg, cause); + } + + public static Bundle createExtrasForService(@NonNull ComponentName service) { + Bundle b = new Bundle(); + b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName()); + return b; + } + + @Nullable + public static String getServiceClassNameFromExtras(@Nullable Bundle extras) { + return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME); + } + } + + /** * Exception used to crash an app process when the system received a RemoteException * while posting a notification of a foreground service. * diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 726064e39778..aaddaa62a32f 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1198,8 +1198,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * Callback called when a particular foreground service type has timed out. * * <p>This callback is meant to give the app a small grace period of a few seconds to finish - * the foreground service of the associated type - if it fails to do so, the app will be - * declared an ANR. + * the foreground service of the associated type - if it fails to do so, the app will crash. * * <p>The foreground service of the associated type can be stopped within the time limit by * {@link android.app.Service#stopSelf()}, diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 9cf83b9c88e9..bb24fd19315e 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -60,3 +60,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "enable_fgs_timeout_crash_behavior" + description: "Enable the new behavior where the app is crashed once an FGS times out." + bug: "339526947" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 9ef8b38666c6..46c9e781bed1 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -21,6 +21,7 @@ import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_US import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.flags.Flags; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -176,6 +177,10 @@ public final class DeviceAdminInfo implements Parcelable { * provisioned into "affiliated" mode when on a Headless System User Mode device. * * <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on. + * + * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * DPCs should set the value of attribute "headless-device-owner-mode" inside the + * "headless-system-user" tag as "affiliated". */ public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; @@ -185,6 +190,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>This mode only allows a single secondary user on the device blocking the creation of * additional secondary users. + * + * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * DPCs should set the value of attribute "headless-device-owner-mode" inside the + * "headless-system-user" tag as "single_user". */ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED) public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; @@ -383,17 +392,30 @@ public final class DeviceAdminInfo implements Parcelable { } mSupportsTransferOwnership = true; } else if (tagName.equals("headless-system-user")) { - String deviceOwnerModeStringValue = - parser.getAttributeValue(null, "device-owner-mode"); + String deviceOwnerModeStringValue = null; + if (Flags.headlessSingleUserCompatibilityFix()) { + deviceOwnerModeStringValue = parser.getAttributeValue( + null, "headless-device-owner-mode"); + } + if (deviceOwnerModeStringValue == null) { + deviceOwnerModeStringValue = + parser.getAttributeValue(null, "device-owner-mode"); + } - if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) { + if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; - } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) { + } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; - } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) { + } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; } else { - throw new XmlPullParserException("headless-system-user mode must be valid"); + if (Flags.headlessSingleUserCompatibilityFix()) { + Log.e(TAG, "Unknown headless-system-user mode: " + + deviceOwnerModeStringValue); + } else { + throw new XmlPullParserException( + "headless-system-user mode must be valid"); + } } } } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 6da96c1fe4a1..3d6ec19299cb 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -313,3 +313,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "headless_single_user_compatibility_fix" + namespace: "enterprise" + description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds" + bug: "338050276" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "headless_single_min_target_sdk" + namespace: "enterprise" + description: "Only allow DPCs targeting Android V to provision into single user mode" + bug: "338588825" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl index 9f09d043a89b..5a1325519699 100644 --- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl +++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl @@ -48,6 +48,8 @@ interface IVirtualDeviceManagerNative { const int POLICY_TYPE_AUDIO = 1; const int POLICY_TYPE_RECENTS = 2; const int POLICY_TYPE_ACTIVITY = 3; + const int POLICY_TYPE_CLIPBOARD = 4; + const int POLICY_TYPE_CAMERA = 5; /** * Returns the IDs for all VirtualDevices where an app with the given is running. @@ -62,4 +64,4 @@ interface IVirtualDeviceManagerNative { * Returns the device policy for the given virtual device and policy type. */ int getDevicePolicy(int deviceId, int policyType); -}
\ No newline at end of file +} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index 7f01a8256cd0..37f419d717c2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -462,6 +462,20 @@ public final class AttributionSource implements Parcelable { } /** + * @return The next package's device Id from its context. + * This device ID is used for permissions checking during attribution source validation. + * + * @hide + */ + public int getNextDeviceId() { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { + return mAttributionSourceState.next[0].deviceId; + } + return Context.DEVICE_ID_DEFAULT; + } + + /** * Checks whether this attribution source can be trusted. That is whether * the app it refers to created it and provided to the attribution chain. * diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java index 3d8ccaaa45cc..c70eff42c732 100644 --- a/core/java/android/credentials/GetCandidateCredentialsResponse.java +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java @@ -18,6 +18,8 @@ package android.credentials; import android.annotation.Hide; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Intent; import android.credentials.selection.GetCredentialProviderData; import android.os.Parcel; @@ -39,6 +41,9 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @NonNull private final List<GetCredentialProviderData> mCandidateProviderDataList; + @Nullable + private final ComponentName mPrimaryProviderComponentName; + @NonNull private final Intent mIntent; @@ -48,13 +53,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @Hide public GetCandidateCredentialsResponse( @NonNull List<GetCredentialProviderData> candidateProviderDataList, - @NonNull Intent intent + @NonNull Intent intent, + @Nullable ComponentName primaryProviderComponentName ) { Preconditions.checkCollectionNotEmpty( candidateProviderDataList, /*valueName=*/ "candidateProviderDataList"); mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList); mIntent = intent; + mPrimaryProviderComponentName = primaryProviderComponentName; } /** @@ -67,6 +74,16 @@ public final class GetCandidateCredentialsResponse implements Parcelable { } /** + * Returns the primary provider component name. + * + * @hide + */ + @Nullable + public ComponentName getPrimaryProviderComponentName() { + return mPrimaryProviderComponentName; + } + + /** * Returns candidate provider data list. * * @hide @@ -83,12 +100,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList); mIntent = in.readTypedObject(Intent.CREATOR); + + mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeTypedList(mCandidateProviderDataList); dest.writeTypedObject(mIntent, flags); + dest.writeTypedObject(mPrimaryProviderComponentName, flags); } @Override diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 6d9b51cbd003..2e1e90c78f3a 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -200,6 +200,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes supportedCaptureSizes.put(format, supportedSizes); } } + + int captureFormat = ImageFormat.UNKNOWN; Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); OutputConfiguration burstCaptureOutputConfig = null; @@ -210,6 +212,12 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } } suitableSurfaceCount++; + + if (Flags.analytics24q3()) { + CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = + CameraExtensionUtils.querySurface(burstCaptureSurface); + captureFormat = burstCaptureSurfaceInfo.mFormat; + } } if (suitableSurfaceCount != config.getOutputConfigurations().size()) { @@ -249,6 +257,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(), config.getExecutor(), sessionId, token, config.getExtension()); + if (Flags.analytics24q3()) { + ret.mStatsAggregator.setCaptureFormat(captureFormat); + } ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); ret.mStatsAggregator.setExtensionType(config.getExtension()); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 3ae319999e35..a4ae398782b4 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -200,10 +200,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { supportedCaptureSizes.put(format, supportedSizes); } } + + int captureFormat = ImageFormat.UNKNOWN; Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); if (burstCaptureSurface != null) { suitableSurfaceCount++; + + if (Flags.analytics24q3()) { + CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = + CameraExtensionUtils.querySurface(burstCaptureSurface); + captureFormat = burstCaptureSurfaceInfo.mFormat; + } } if (suitableSurfaceCount != config.getOutputConfigurations().size()) { @@ -258,6 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { extensionChars.getAvailableCaptureResultKeys(config.getExtension()), config.getExtension()); + if (Flags.analytics24q3()) { + session.mStatsAggregator.setCaptureFormat(captureFormat); + } session.mStatsAggregator.setClientName(ctx.getOpPackageName()); session.mStatsAggregator.setExtensionType(config.getExtension()); diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java index 3050a51d7955..c75e4187b7bb 100644 --- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java +++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java @@ -70,6 +70,23 @@ public class ExtensionSessionStatsAggregator { } /** + * Set the capture format. + * + * @param format Format of requested capture. + */ + public void setCaptureFormat(int format) { + synchronized (mLock) { + if (mIsDone) { + return; + } + if (DEBUG) { + Log.v(TAG, "Setting capture format: " + format); + } + mStats.captureFormat = format; + } + } + + /** * Set extension type. * * @param extensionType Type of extension. Must match one of @@ -116,7 +133,8 @@ public class ExtensionSessionStatsAggregator { + " cameraId: '" + stats.cameraId + "'\n" + " clientName: '" + stats.clientName + "'\n" + " type: '" + stats.type + "'\n" - + " isAdvanced: '" + stats.isAdvanced + "'\n"; + + " isAdvanced: '" + stats.isAdvanced + "'\n" + + " captureFormat: '" + stats.captureFormat + "'\n"; } /** diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 8f78032435a8..45b316aa5498 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -171,9 +171,9 @@ interface IInputManager { void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor); // Add a runtime association between the input device and display, using device's port. - void addUniqueIdAssociation(in String inputPort, in String displayUniqueId); + void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId); // Remove the runtime association between the input device and display, using device's port. - void removeUniqueIdAssociation(in String inputPort); + void removeUniqueIdAssociationByPort(in String inputPort); InputSensorInfo[] getSensorList(int deviceId); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 57004bca80b6..7527aa7197e9 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -1094,10 +1094,9 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to addUniqueIdAssociationByPort - public void addUniqueIdAssociation(@NonNull String inputPort, + public void addUniqueIdAssociationByPort(@NonNull String inputPort, @NonNull String displayUniqueId) { - mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId); + mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } /** @@ -1110,9 +1109,8 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort - public void removeUniqueIdAssociation(@NonNull String inputPort) { - mGlobal.removeUniqueIdAssociation(inputPort); + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { + mGlobal.removeUniqueIdAssociationByPort(inputPort); } /** diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index cb3af2b915bb..fcd5a3ed06c0 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -1445,22 +1445,23 @@ public final class InputManagerGlobal { } /** - * @see InputManager#addUniqueIdAssociation(String, String) + * @see InputManager#addUniqueIdAssociationByPort(String, String) */ - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { try { - mIm.addUniqueIdAssociation(inputPort, displayUniqueId); + mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * @see InputManager#removeUniqueIdAssociation(String) + * @see InputManager#removeUniqueIdAssociationByPort(String) */ - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { try { - mIm.removeUniqueIdAssociation(inputPort); + mIm.removeUniqueIdAssociationByPort(inputPort); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 9a63394d3ca1..49ab15a40a8e 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -429,10 +429,9 @@ public class BaseBundle { "Lazy values ref count below 0"); // No more lazy values in mMap, so we can recycle the parcel early rather than // waiting for the next GC run - if (mLazyValues == 0) { - Preconditions.checkState(mWeakParcelledData.get() != null, - "Parcel recycled earlier than expected"); - recycleParcel(mWeakParcelledData.get()); + Parcel parcel = mWeakParcelledData.get(); + if (mLazyValues == 0 && parcel != null) { + recycleParcel(parcel); mWeakParcelledData = null; } } diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 51758aa32102..ee5e533364ff 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -81,8 +81,15 @@ flag { } flag { - name: "report_primary_auth_attempts" - namespace: "biometrics" - description: "Report primary auth attempts from LockSettingsService" - bug: "285053096" + name: "report_primary_auth_attempts" + namespace: "biometrics" + description: "Report primary auth attempts from LockSettingsService" + bug: "285053096" +} + +flag { + name: "dump_attestation_verifications" + namespace: "hardware_backed_security" + description: "Add a dump capability for attestation_verification service" + bug: "335498868" } diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 92f2c321c1db..3cd705a3c19c 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -86,7 +86,8 @@ public final class CredentialProviderInfoFactory { @NonNull Context context, @NonNull ComponentName serviceComponent, int userId, - boolean isSystemProvider) + boolean isSystemProvider, + boolean isPrimary) throws PackageManager.NameNotFoundException { return create( context, @@ -94,7 +95,7 @@ public final class CredentialProviderInfoFactory { isSystemProvider, /* disableSystemAppVerificationForTests= */ false, /* isEnabled= */ false, - /* isPrimary= */ false); + isPrimary); } /** diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index db665a92ec5c..c4becea462d5 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -1392,10 +1392,6 @@ public final class DisplayCutout { private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets, Rect[] bounds) { - if (displayW == displayH) { - throw new UnsupportedOperationException("not implemented: display=" + displayW + "x" - + displayH + " bounding rects=" + Arrays.toString(bounds)); - } int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide( displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT)); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bfe4e6f3cc9b..9cc4191d0c80 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1978,6 +1978,13 @@ public final class AutofillManager { if (Objects.equals(mLastAutofilledData.get(id), value)) { view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, id, mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the + // server side. Do nothing here since it's just logging, but it's + // possible follow-up actions may fail. + } } else { view.setAutofilled(false, false); mLastAutofilledData.remove(id); @@ -2978,6 +2985,13 @@ public final class AutofillManager { mLastAutofilledData.put(view.getAutofillId(), targetValue); } view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the server side. + // Do nothing here since it's just logging, but it's possible follow-up actions may + // fail. + } } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 1a9322e6f2b8..2039b4d5c1bd 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -49,6 +49,7 @@ oneway interface IAutoFillManager { void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int action, int flags, int userId); void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId); + void setViewAutofilled(int sessionId, in AutofillId id, int userId); void finishSession(int sessionId, int userId, int commitReason); void cancelSession(int sessionId, int userId); void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c5114b9550db..fb3e0831fdc9 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -720,6 +720,20 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; + + /** + * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and + * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo + * extras bundle. These methods do not set or check the Android V property since the Android V + * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return + * the correct value for EditorInfo created by Android V TextView, the extras bundle value + * should be set. This is the extras bundle key. + * + * @hide + */ + public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY = + "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED"; + /** * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index fa9458d01681..56e5bcf79933 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -62,6 +62,17 @@ flag { } flag { + name: "use_input_method_info_safe_list" + namespace: "input_method" + description: "Use InputMethodInfoSafeList for more reliable binder IPCs" + bug: "339761278" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ime_switcher_revamp" is_exported: true namespace: "input_method" diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fb1c331171a6..78dd3b18c2a6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -27,6 +27,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; +import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; @@ -10062,9 +10063,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); - if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled() - && isAutoHandwritingEnabled()) { - outAttrs.setStylusHandwritingEnabled(true); + if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) { + boolean handwritingEnabled = isAutoHandwritingEnabled(); + outAttrs.setStylusHandwritingEnabled(handwritingEnabled); + // AndroidX Core library 1.13.0 introduced + // EditorInfoCompat#setStylusHandwritingEnabled and + // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the + // EditorInfo extras bundle. These methods do not set or check the Android V + // property since the Android V SDK was not yet available. In order for + // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for + // EditorInfo created by Android V TextView, the extras bundle value is also set + // here. + if (outAttrs.extras == null) { + outAttrs.extras = new Bundle(); + } + outAttrs.extras.putBoolean( + STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled); } ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); gestures.add(SelectGesture.class); diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp index 7f630cb27972..5d7b33e89d19 100644 --- a/core/jni/android_hardware_display_DisplayViewport.cpp +++ b/core/jni/android_hardware_display_DisplayViewport.cpp @@ -59,7 +59,8 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject static const jclass intClass = FindClassOrDie(env, "java/lang/Integer"); static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B"); - viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); + viewport->displayId = ui::LogicalDisplayId{ + env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)}; viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive); jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); viewport->orientation = static_cast<ui::Rotation>(orientation); diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index bed776836043..69f633420a0d 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -165,8 +165,8 @@ bool NativeInputWindowHandle::updateInfo() { mInfo.ownerUid = gui::Uid{ static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))}; mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>"); - mInfo.displayId = env->GetIntField(obj, - gInputWindowHandleClassInfo.displayId); + mInfo.displayId = + ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)}; jobject inputApplicationHandleObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle); diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp index ca8752f93e11..06e0d2d3ceaa 100644 --- a/core/jni/android_view_KeyEvent.cpp +++ b/core/jni/android_view_KeyEvent.cpp @@ -135,8 +135,8 @@ KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj) { jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime); KeyEvent event; - event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode, - metaState, repeatCount, downTime, eventTime); + event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags, + keyCode, scanCode, metaState, repeatCount, downTime, eventTime); return event; } diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 3e3af40a6530..f914bee8da1b 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -22,7 +22,6 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> #include <attestation/HmacKeyManager.h> -#include <gui/constants.h> #include <input/Input.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> @@ -367,8 +366,8 @@ static jlong android_view_MotionEvent_nativeInitialize( ui::Transform transform; transform.set(xOffset, yOffset); ui::Transform identityTransform; - event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0, - flags, edgeFlags, metaState, buttonState, + event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId}, + INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState, static_cast<MotionClassification>(classification), transform, xPrecision, yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos, @@ -646,13 +645,13 @@ static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA j static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); - return event->getDisplayId(); + return static_cast<jint>(event->getDisplayId().val()); } static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint displayId) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); - return event->setDisplayId(displayId); + event->setDisplayId(ui::LogicalDisplayId{displayId}); } static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) { diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp index bc69d1e67668..c39d5e20aa1c 100644 --- a/core/jni/android_window_WindowInfosListener.cpp +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -60,7 +60,7 @@ jobject fromDisplayInfo(JNIEnv* env, gui::DisplayInfo displayInfo) { } ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues)); return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor, - displayInfo.displayId, displayInfo.logicalWidth, + displayInfo.displayId.val(), displayInfo.logicalWidth, displayInfo.logicalHeight, matrixObj.get()); } diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 043385513027..bf2fddab3d41 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -329,7 +329,8 @@ static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) { InputDeviceInfo info = InputDeviceInfo(); info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(), - "keyboard " + std::to_string(keyboardId), true, false, 0); + "keyboard " + std::to_string(keyboardId), true, false, + ui::ADISPLAY_ID_DEFAULT); info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); info.setKeyCharacterMap(*charMap); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 14388a6d42ff..5b0e6b9c49a1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -855,7 +855,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!parentInfo.isVisible()) { // Only making the TaskContainer invisible and drops the other info, and perform the // update when the next time the Task becomes visible. - taskContainer.setIsVisible(false); + if (taskContainer.isVisible()) { + taskContainer.setInvisible(); + } return; } @@ -3228,10 +3230,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); - if (parentInfo != null) { - dividerPresenter.updateDivider( - wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); - } + dividerPresenter.updateDivider( + wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); } @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index a68373832a14..c708da97d908 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -78,16 +78,7 @@ class TaskContainer { private TaskFragmentContainer mAlwaysOnTopOverlayContainer; @NonNull - private final Configuration mConfiguration; - - private int mDisplayId; - - private boolean mIsVisible; - - private boolean mHasDirectActivity; - - @Nullable - private TaskFragmentParentInfo mTaskFragmentParentInfo; + private TaskFragmentParentInfo mInfo; /** * TaskFragments that the organizer has requested to be closed. They should be removed when @@ -131,12 +122,14 @@ class TaskContainer { mTaskId = taskId; final TaskProperties taskProperties = TaskProperties .getTaskPropertiesFromActivity(activityInTask); - mConfiguration = taskProperties.getConfiguration(); - mDisplayId = taskProperties.getDisplayId(); - // Note that it is always called when there's a new Activity is started, which implies - // the host task is visible and has an activity in the task. - mIsVisible = true; - mHasDirectActivity = true; + mInfo = new TaskFragmentParentInfo( + taskProperties.getConfiguration(), + taskProperties.getDisplayId(), + // Note that it is always called when there's a new Activity is started, which + // implies the host task is visible and has an activity in the task. + true /* visible */, + true /* hasDirectActivity */, + null /* decorSurface */); } int getTaskId() { @@ -144,43 +137,39 @@ class TaskContainer { } int getDisplayId() { - return mDisplayId; + return mInfo.getDisplayId(); } boolean isVisible() { - return mIsVisible; + return mInfo.isVisible(); } - void setIsVisible(boolean visible) { - mIsVisible = visible; + void setInvisible() { + mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(), + false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface()); } boolean hasDirectActivity() { - return mHasDirectActivity; + return mInfo.hasDirectActivity(); } @NonNull Rect getBounds() { - return mConfiguration.windowConfiguration.getBounds(); + return mInfo.getConfiguration().windowConfiguration.getBounds(); } @NonNull TaskProperties getTaskProperties() { - return new TaskProperties(mDisplayId, mConfiguration); + return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration()); } void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { - // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields. - mConfiguration.setTo(info.getConfiguration()); - mDisplayId = info.getDisplayId(); - mIsVisible = info.isVisible(); - mHasDirectActivity = info.hasDirectActivity(); - mTaskFragmentParentInfo = info; + mInfo = info; } - @Nullable + @NonNull TaskFragmentParentInfo getTaskFragmentParentInfo() { - return mTaskFragmentParentInfo; + return mInfo; } /** @@ -196,8 +185,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mConfiguration.diffPublicOnly(configuration) != 0 - || mDisplayId != info.getDisplayId(); + return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + || mInfo.getDisplayId() != info.getDisplayId(); } /** @@ -224,7 +213,7 @@ class TaskContainer { } boolean isInPictureInPicture() { - return isInPictureInPicture(mConfiguration); + return isInPictureInPicture(mInfo.getConfiguration()); } private static boolean isInPictureInPicture(@NonNull Configuration configuration) { @@ -237,7 +226,7 @@ class TaskContainer { @WindowingMode private int getWindowingMode() { - return mConfiguration.windowConfiguration.getWindowingMode(); + return mInfo.getConfiguration().windowConfiguration.getWindowingMode(); } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index ceeed88fa440..ea30af5c3d5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -159,6 +159,8 @@ public class BubbleData { bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey()); } } + bubbleBarUpdate.showOverflowChanged = showOverflowChanged; + bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty(); return bubbleBarUpdate; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 7bceb2ce99b0..be88b3497000 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -536,7 +536,8 @@ public class BubbleStackView extends FrameLayout private OnClickListener mBubbleClickListener = new OnClickListener() { @Override public void onClick(View view) { - mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging. + // If the touch ended in a click, we're no longer dragging. + onDraggingEnded(); // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust @@ -719,8 +720,7 @@ public class BubbleStackView extends FrameLayout mDismissView.hide(); } - mIsDraggingStack = false; - mMagnetizedObject = null; + onDraggingEnded(); // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); @@ -1096,6 +1096,7 @@ public class BubbleStackView extends FrameLayout } else { maybeShowStackEdu(); } + onDraggingEnded(); }); animate() @@ -1153,6 +1154,14 @@ public class BubbleStackView extends FrameLayout } /** + * Reset state related to dragging. + */ + private void onDraggingEnded() { + mIsDraggingStack = false; + mMagnetizedObject = null; + } + + /** * Sets whether or not the stack should become temporarily invisible by moving off the side of * the screen. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java index 6980c6f01e5d..ec3c6013e544 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java @@ -52,6 +52,8 @@ public class BubbleBarUpdate implements Parcelable { public BubbleBarLocation bubbleBarLocation; @Nullable public Point expandedViewDropTargetSize; + public boolean showOverflowChanged; + public boolean showOverflow; // This is only populated if bubbles have been removed. public List<RemovedBubble> removedBubbles = new ArrayList<>(); @@ -92,6 +94,8 @@ public class BubbleBarUpdate implements Parcelable { BubbleBarLocation.class); expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(), Point.class); + showOverflowChanged = parcel.readBoolean(); + showOverflow = parcel.readBoolean(); } /** @@ -107,7 +111,8 @@ public class BubbleBarUpdate implements Parcelable { || suppressedBubbleKey != null || unsupressedBubbleKey != null || !currentBubbleList.isEmpty() - || bubbleBarLocation != null; + || bubbleBarLocation != null + || showOverflowChanged; } @NonNull @@ -128,6 +133,8 @@ public class BubbleBarUpdate implements Parcelable { + " currentBubbleList=" + currentBubbleList + " bubbleBarLocation=" + bubbleBarLocation + " expandedViewDropTargetSize=" + expandedViewDropTargetSize + + " showOverflowChanged=" + showOverflowChanged + + " showOverflow=" + showOverflow + " }"; } @@ -152,6 +159,8 @@ public class BubbleBarUpdate implements Parcelable { parcel.writeParcelableList(currentBubbleList, flags); parcel.writeParcelable(bubbleBarLocation, flags); parcel.writeParcelable(expandedViewDropTargetSize, flags); + parcel.writeBoolean(showOverflowChanged); + parcel.writeBoolean(showOverflow); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index ea86c79ae736..dba0a985411d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -16,10 +16,12 @@ package com.android.wm.shell.common.pip import android.app.ActivityTaskManager +import android.app.AppGlobals import android.app.RemoteAction import android.app.WindowConfiguration import android.content.ComponentName import android.content.Context +import android.content.pm.PackageManager import android.os.RemoteException import android.os.SystemProperties import android.util.DisplayMetrics @@ -136,8 +138,23 @@ object PipUtils { } } + private var isPip2ExperimentEnabled: Boolean? = null + + /** + * Returns true if PiP2 implementation should be used. Besides the trunk stable flag, + * system property can be used to override this read only flag during development. + * It's currently limited to phone form factor, i.e., not enabled on ARC / TV. + */ @JvmStatic - val isPip2ExperimentEnabled: Boolean - get() = Flags.enablePip2Implementation() || SystemProperties.getBoolean( - "wm_shell.pip2", false) + fun isPip2ExperimentEnabled(): Boolean { + if (isPip2ExperimentEnabled == null) { + val isArc = AppGlobals.getPackageManager().hasSystemFeature( + "org.chromium.arc", 0) + val isTv = AppGlobals.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_LEANBACK, 0) + isPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) || + (Flags.enablePip2Implementation() && !isArc && !isTv) + } + return isPip2ExperimentEnabled as Boolean + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 4d02ec26e18e..968b27b62490 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -426,7 +426,8 @@ public class DefaultMixedHandler implements MixedTransitionHandler, ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Converting mixed transition into a keyguard transition"); // Consume the original mixed transition - onTransitionConsumed(transition, false, null); + mActiveTransitions.remove(mixed); + mixed.onTransitionConsumed(transition, false, null); return true; } else { // Keyguard handler cannot handle it, process through original mixed diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h index 1a5b938d6eed..31c9db7ca4fb 100644 --- a/libs/hwui/ColorFilter.h +++ b/libs/hwui/ColorFilter.h @@ -23,17 +23,42 @@ #include "GraphicsJNI.h" #include "SkColorFilter.h" -#include "SkiaWrapper.h" namespace android { namespace uirenderer { -class ColorFilter : public SkiaWrapper<SkColorFilter> { +class ColorFilter : public VirtualLightRefBase { public: static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); } + sk_sp<SkColorFilter> getInstance() { + if (mInstance != nullptr && shouldDiscardInstance()) { + mInstance = nullptr; + } + + if (mInstance == nullptr) { + mInstance = createInstance(); + if (mInstance) { + mInstance = mInstance->makeWithWorkingColorSpace(SkColorSpace::MakeSRGB()); + } + mGenerationId++; + } + return mInstance; + } + + virtual bool shouldDiscardInstance() const { return false; } + + void discardInstance() { mInstance = nullptr; } + + [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; } + protected: ColorFilter() = default; + virtual sk_sp<SkColorFilter> createInstance() = 0; + +private: + sk_sp<SkColorFilter> mInstance = nullptr; + int32_t mGenerationId = 0; }; class BlendModeColorFilter : public ColorFilter { diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h deleted file mode 100644 index bd0e35aadbb4..000000000000 --- a/libs/hwui/SkiaWrapper.h +++ /dev/null @@ -1,56 +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. - */ - -#ifndef SKIA_WRAPPER_H_ -#define SKIA_WRAPPER_H_ - -#include <SkRefCnt.h> -#include <utils/RefBase.h> - -namespace android::uirenderer { - -template <typename T> -class SkiaWrapper : public VirtualLightRefBase { -public: - sk_sp<T> getInstance() { - if (mInstance != nullptr && shouldDiscardInstance()) { - mInstance = nullptr; - } - - if (mInstance == nullptr) { - mInstance = createInstance(); - mGenerationId++; - } - return mInstance; - } - - virtual bool shouldDiscardInstance() const { return false; } - - void discardInstance() { mInstance = nullptr; } - - [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; } - -protected: - virtual sk_sp<T> createInstance() = 0; - -private: - sk_sp<T> mInstance = nullptr; - int32_t mGenerationId = 0; -}; - -} // namespace android::uirenderer - -#endif // SKIA_WRAPPER_H_ diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 6a465442c2b4..5cf5a1d00815 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -117,7 +117,7 @@ FloatPoint MouseCursorController::getPosition() const { return {mLocked.pointerX, mLocked.pointerY}; } -int32_t MouseCursorController::getDisplayId() const { +ui::LogicalDisplayId MouseCursorController::getDisplayId() const { std::scoped_lock lock(mLock); return mLocked.viewport.displayId; } @@ -467,10 +467,10 @@ void MouseCursorController::startAnimationLocked() REQUIRES(mLock) { std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1); /* - * Using -1 for displayId here to avoid removing the callback + * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback * if a TouchSpotController with the same display is removed. */ - mContext.addAnimationCallback(-1, func); + mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func); } } // namespace android diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 00dc0854440e..dc7e8ca16c8a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -47,7 +47,7 @@ public: void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; - int32_t getDisplayId() const; + ui::LogicalDisplayId getDisplayId() const; void fade(PointerControllerInterface::Transition transition); void unfade(PointerControllerInterface::Transition transition); void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index f97992f7c9d1..cca1b07c3118 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -142,7 +142,7 @@ std::optional<FloatRect> PointerController::getBounds() const { } void PointerController::move(float deltaX, float deltaY) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -153,7 +153,7 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -164,7 +164,7 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { std::scoped_lock lock(getLock()); @@ -173,7 +173,7 @@ FloatPoint PointerController::getPosition() const { } } -int32_t PointerController::getDisplayId() const { +ui::LogicalDisplayId PointerController::getDisplayId() const { return mCursorController.getDisplayId(); } @@ -202,7 +202,7 @@ void PointerController::setPresentation(Presentation presentation) { } void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) { + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -286,7 +286,7 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCursorController.setCustomPointerIcon(icon); } -void PointerController::setSkipScreenshot(int32_t displayId, bool skip) { +void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { std::scoped_lock lock(getLock()); if (skip) { mLocked.displaysToSkipScreenshot.insert(displayId); @@ -300,14 +300,14 @@ void PointerController::doInactivityTimeout() { } void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) { - std::unordered_set<int32_t> displayIdSet; + std::unordered_set<ui::LogicalDisplayId> displayIdSet; for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); } std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { - int32_t displayId = it->first; + ui::LogicalDisplayId displayId = it->first; if (!displayIdSet.count(displayId)) { /* * Ensures that an in-progress animation won't dereference @@ -326,7 +326,8 @@ void PointerController::onDisplayInfosChangedLocked( mLocked.mDisplayInfos = displayInfo; } -const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { +const ui::Transform& PointerController::getTransformForDisplayLocked( + ui::LogicalDisplayId displayId) const { const auto& di = mLocked.mDisplayInfos; auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { return info.displayId == displayId; @@ -339,7 +340,8 @@ std::string PointerController::dump() { std::scoped_lock lock(getLock()); dump += StringPrintf(INDENT2 "Presentation: %s\n", ftl::enum_string(mLocked.presentation).c_str()); - dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId); + dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n", + mLocked.pointerDisplayId.toString().c_str()); dump += StringPrintf(INDENT2 "Viewports:\n"); for (const auto& info : mLocked.mDisplayInfos) { info.dump(dump, INDENT3); diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index eaf34d527396..70e5c2455d81 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -55,18 +55,18 @@ public: void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; - int32_t getDisplayId() const override; + ui::LogicalDisplayId getDisplayId() const override; void fade(Transition transition) override; void unfade(Transition transition) override; void setDisplayViewport(const DisplayViewport& viewport) override; void setPresentation(Presentation presentation) override; void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) override; + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override; void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(int32_t displayId, bool skip) override; + void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -109,11 +109,11 @@ private: struct Locked { Presentation presentation; - int32_t pointerDisplayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE; std::vector<gui::DisplayInfo> mDisplayInfos; - std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; - std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot; + std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers; + std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot; } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { @@ -132,7 +132,8 @@ private: sp<DisplayInfoListener> mDisplayInfoListener; const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener; - const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); + const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const + REQUIRES(getLock()); void clearSpotsLocked() REQUIRES(getLock()); }; @@ -148,7 +149,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { @@ -176,7 +177,7 @@ public: FloatPoint getPosition() const override { LOG_ALWAYS_FATAL("Should not be called"); } - int32_t getDisplayId() const override { + ui::LogicalDisplayId getDisplayId() const override { LOG_ALWAYS_FATAL("Should not be called"); } void fade(Transition) override { @@ -212,7 +213,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp index 15c35176afce..747eb8e5ad1b 100644 --- a/libs/input/PointerControllerContext.cpp +++ b/libs/input/PointerControllerContext.cpp @@ -138,12 +138,12 @@ int PointerControllerContext::LooperCallback::handleEvent(int /* fd */, int even return 1; // keep the callback } -void PointerControllerContext::addAnimationCallback(int32_t displayId, +void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { mAnimator.addCallback(displayId, callback); } -void PointerControllerContext::removeAnimationCallback(int32_t displayId) { +void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) { mAnimator.removeCallback(displayId); } @@ -161,14 +161,14 @@ void PointerControllerContext::PointerAnimator::initializeDisplayEventReceiver() } } -void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId, +void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { std::scoped_lock lock(mLock); mLocked.callbacks[displayId] = callback; startAnimationLocked(); } -void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) { +void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) { std::scoped_lock lock(mLock); auto it = mLocked.callbacks.find(displayId); if (it == mLocked.callbacks.end()) { diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index e893c49d80e5..d42214883d3a 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -72,12 +72,13 @@ protected: virtual ~PointerControllerPolicyInterface() {} public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) = 0; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) = 0; + ui::LogicalDisplayId displayId) = 0; virtual PointerIconStyle getDefaultPointerIconId() = 0; virtual PointerIconStyle getDefaultStylusIconId() = 0; virtual PointerIconStyle getCustomPointerIconId() = 0; @@ -102,7 +103,7 @@ public: nsecs_t getAnimationTime(); - void clearSpotsByDisplay(int32_t displayId); + void clearSpotsByDisplay(ui::LogicalDisplayId displayId); void setHandlerController(std::shared_ptr<PointerController> controller); void setCallbackController(std::shared_ptr<PointerController> controller); @@ -112,8 +113,9 @@ public: void handleDisplayEvents(); - void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeAnimationCallback(int32_t displayId); + void addAnimationCallback(ui::LogicalDisplayId displayId, + std::function<bool(nsecs_t)> callback); + void removeAnimationCallback(ui::LogicalDisplayId displayId); class MessageHandler : public virtual android::MessageHandler { public: @@ -136,8 +138,8 @@ private: public: PointerAnimator(PointerControllerContext& context); - void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeCallback(int32_t displayId); + void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback); + void removeCallback(ui::LogicalDisplayId displayId); void handleVsyncEvents(); nsecs_t getAnimationTimeLocked(); @@ -148,7 +150,7 @@ private: bool animationPending{false}; nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)}; - std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks; + std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks; } mLocked GUARDED_BY(mLock); DisplayEventReceiver mDisplayEventReceiver; diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 0baa9291f54d..af499390d390 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -19,9 +19,9 @@ #include "SpriteController.h" -#include <log/log.h> -#include <utils/String8.h> +#include <android-base/logging.h> #include <gui/Surface.h> +#include <utils/String8.h> namespace android { @@ -340,13 +340,14 @@ void SpriteController::ensureSurfaceComposerClient() { } } -sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId, +sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, + ui::LogicalDisplayId displayId, bool hideOnMirrored) { ensureSurfaceComposerClient(); const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId); if (parent == nullptr) { - ALOGE("Failed to get the parent surface for pointers on display %d", displayId); + LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId; } int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow; @@ -475,7 +476,7 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } -void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { +void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) { AutoMutex _l(mController.mLock); if (mLocked.state.displayId != displayId) { diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index fdb15506fd0c..070c90c372ff 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -95,7 +95,7 @@ public: virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; /* Sets the id of the display where the sprite should be shown. */ - virtual void setDisplayId(int32_t displayId) = 0; + virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0; /* Sets the flag to hide sprite on mirrored displays. * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */ @@ -115,7 +115,7 @@ public: */ class SpriteController { public: - using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>; + using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>; SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent); SpriteController(const SpriteController&) = delete; SpriteController& operator=(const SpriteController&) = delete; @@ -174,7 +174,7 @@ private: int32_t layer{0}; float alpha{1.0f}; SpriteTransformationMatrix transformationMatrix; - int32_t displayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT}; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth{0}; @@ -208,7 +208,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); - virtual void setDisplayId(int32_t displayId); + virtual void setDisplayId(ui::LogicalDisplayId displayId); virtual void setSkipScreenshot(bool skip); inline const SpriteState& getStateLocked() const { @@ -273,7 +273,7 @@ private: void doDisposeSurfaces(); void ensureSurfaceComposerClient(); - sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId, + sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId, bool hideOnMirrored); }; diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index 530d54129791..7462481f8779 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -40,7 +40,7 @@ namespace android { // --- Spot --- void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY, - int32_t displayId, bool skipScreenshot) { + ui::LogicalDisplayId displayId, bool skipScreenshot) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); @@ -69,7 +69,8 @@ void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const // --- TouchSpotController --- -TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context) +TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId, + PointerControllerContext& context) : mDisplayId(displayId), mContext(context) { mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId); } @@ -94,7 +95,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 const PointerCoords& c = spotCoords[spotIdToIndex[id]]; ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id); } #endif @@ -274,7 +275,7 @@ void TouchSpotController::dump(std::string& out, const char* prefix) const { out += prefix; out += "SpotController:\n"; out += prefix; - StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId); + StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str()); std::scoped_lock lock(mLock); out += prefix; StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating)); diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h index 608653c6a2e7..ac37fa430249 100644 --- a/libs/input/TouchSpotController.h +++ b/libs/input/TouchSpotController.h @@ -29,7 +29,7 @@ namespace android { */ class TouchSpotController { public: - TouchSpotController(int32_t displayId, PointerControllerContext& context); + TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context); ~TouchSpotController(); void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, bool skipScreenshot); @@ -59,7 +59,7 @@ private: y(0.0f), mLastIcon(nullptr) {} - void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId, + void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId, bool skipScreenshot); void dump(std::string& out, const char* prefix = "") const; @@ -67,7 +67,7 @@ private: const SpriteIcon* mLastIcon; }; - int32_t mDisplayId; + ui::LogicalDisplayId mDisplayId; mutable std::mutex mLock; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 3bc0e24b6e2e..7a133801f514 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -52,12 +52,13 @@ std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) { class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface { public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) override; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) override; + ui::LogicalDisplayId displayId) override; virtual PointerIconStyle getDefaultPointerIconId() override; virtual PointerIconStyle getDefaultStylusIconId() override; virtual PointerIconStyle getCustomPointerIconId() override; @@ -73,13 +74,13 @@ private: bool additionalMouseResourcesLoaded{false}; }; -void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { +void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) { loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); pointerIconLoaded = true; } void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, - int32_t) { + ui::LogicalDisplayId) { loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); @@ -88,7 +89,7 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) { SpriteIcon icon; PointerAnimation anim; @@ -165,7 +166,7 @@ protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT); + void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -204,7 +205,7 @@ PointerControllerTest::~PointerControllerTest() { mThread.join(); } -void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) { +void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) { DisplayViewport viewport; viewport.displayId = displayId; viewport.logicalRight = 1600; @@ -334,23 +335,23 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true); + mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false); + mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h index 0867221d9eed..21628fb9f72c 100644 --- a/libs/input/tests/mocks/MockSprite.h +++ b/libs/input/tests/mocks/MockSprite.h @@ -33,7 +33,7 @@ public: MOCK_METHOD(void, setLayer, (int32_t), (override)); MOCK_METHOD(void, setAlpha, (float), (override)); MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override)); - MOCK_METHOD(void, setDisplayId, (int32_t), (override)); + MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override)); MOCK_METHOD(void, setSkipScreenshot, (bool), (override)); }; diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h index 62f1d65e77a5..9ef6b7c3b480 100644 --- a/libs/input/tests/mocks/MockSpriteController.h +++ b/libs/input/tests/mocks/MockSpriteController.h @@ -27,7 +27,7 @@ class MockSpriteController : public SpriteController { public: MockSpriteController(sp<Looper> looper) - : SpriteController(looper, 0, [](int) { return nullptr; }) {} + : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {} ~MockSpriteController() {} MOCK_METHOD(sp<Sprite>, createSprite, (), (override)); diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 50ebdd5e3ce7..0da32bddd928 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -18,6 +18,7 @@ package com.android.credentialmanager.autofill import android.app.PendingIntent import android.app.assist.AssistStructure +import android.content.ComponentName import android.content.Context import android.credentials.CredentialManager import android.credentials.GetCredentialRequest @@ -34,6 +35,10 @@ import android.os.CancellationSignal import android.os.OutcomeReceiver import android.os.ResultReceiver import android.service.autofill.AutofillService +import com.android.credentialmanager.model.get.ProviderInfo +import androidx.core.graphics.drawable.toBitmap +import com.android.credentialmanager.model.get.ActionEntryInfo +import com.android.credentialmanager.model.EntryInfo import android.service.autofill.Dataset import android.service.autofill.Field import android.service.autofill.FillCallback @@ -63,7 +68,8 @@ import com.android.credentialmanager.getflow.ProviderDisplayInfo import com.android.credentialmanager.getflow.toProviderDisplayInfo import com.android.credentialmanager.ktx.credentialEntry import com.android.credentialmanager.model.CredentialType -import com.android.credentialmanager.model.get.CredentialEntryInfo +import java.util.ArrayList +import java.util.Objects import java.util.concurrent.Executors import org.json.JSONException import org.json.JSONObject @@ -121,8 +127,11 @@ class CredentialAutofillService : AutofillService() { val responseClientState = Bundle() responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false) - val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId, - requestId, resultReceiver, responseClientState) + val uniqueAutofillIdsForRequest: MutableSet<AutofillId> = mutableSetOf() + val getCredRequest: GetCredentialRequest? = getCredManRequest( + structure, sessionId, + requestId, resultReceiver, responseClientState, uniqueAutofillIdsForRequest + ) // TODO(b/324635774): Use callback for validating. If the request is coming // directly from the view, there should be a corresponding callback, otherwise // we should fail fast, @@ -132,14 +141,17 @@ class CredentialAutofillService : AutofillService() { return } val credentialManager: CredentialManager = - getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, GetCandidateCredentialsException> { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResult") - val fillResponse = convertToFillResponse(result, request, - responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest)) + val fillResponse = convertToFillResponse( + result, request, + responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest), + uniqueAutofillIdsForRequest + ) if (fillResponse != null) { callback.onSuccess(fillResponse) } else { @@ -195,58 +207,131 @@ class CredentialAutofillService : AutofillService() { private fun convertToFillResponse( getCredResponse: GetCandidateCredentialsResponse, - filLRequest: FillRequest, + fillRequest: FillRequest, responseClientState: Bundle, typePriorityMap: Map<String, Int>, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): FillResponse? { val candidateProviders = getCredResponse.candidateProviderDataList if (candidateProviders.isEmpty()) { return null } - + val primaryProviderComponentName = getCredResponse.primaryProviderComponentName val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders) val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> = - mapAutofillIdToProviders(candidateProviders) + mapAutofillIdToProviders( + uniqueAutofillIdsForRequest, + candidateProviders, + primaryProviderComponentName + ) val fillResponseBuilder = FillResponse.Builder() fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE) - var validFillResponse = false autofillIdToProvidersMap.forEach { (autofillId, providers) -> - validFillResponse = processProvidersForAutofillId( - filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder, - getCredResponse.intent, typePriorityMap) - .or(validFillResponse) + var credentialDatasetAdded = addCredentialDatasetsForAutofillId(fillRequest, + autofillId, providers, entryIconMap, fillResponseBuilder, typePriorityMap) + if (!credentialDatasetAdded && primaryProviderComponentName != null) { + val providerList = GetFlowUtils.toProviderList( + providers, + this@CredentialAutofillService + ) + val primaryProviderInfo = + providerList.find { provider -> primaryProviderComponentName + .flattenToString().equals(provider.id) } + if (primaryProviderInfo != null) { + addActionDatasetsForAutofillId( + fillRequest, + autofillId, + primaryProviderInfo, + fillResponseBuilder + ) + } + } } - if (!validFillResponse) { - return null + for (autofillId in uniqueAutofillIdsForRequest) { + addMoreOptionsDataset( + fillRequest, + autofillId, + fillResponseBuilder, + getCredResponse.intent.putExtra( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, ArrayList(candidateProviders) + ) + ) } fillResponseBuilder.setClientState(responseClientState) return fillResponseBuilder.build() } - private fun processProvidersForAutofillId( - filLRequest: FillRequest, - autofillId: AutofillId, - providerDataList: ArrayList<GetCredentialProviderData>, - entryIconMap: Map<String, Icon>, - fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent, - typePriorityMap: Map<String, Int>, + private fun addActionDatasetsForAutofillId( + fillRequest: FillRequest, + autofillId: AutofillId, + primaryProvider: ProviderInfo, + fillResponseBuilder: FillResponse.Builder, + ): Boolean { + var index = 0 + var datasetAdded = false + primaryProvider.actionEntryList.forEach { actionEntry -> + if (index >= maxDatasetDisplayLimit(primaryProvider.actionEntryList.size)) { + return@forEach + } + val pendingIntent = actionEntry.pendingIntent + if (pendingIntent == null) { + Log.e(TAG, "Pending intent for action chip is null") + return@forEach + } + + val icon: Icon? = Icon.createWithBitmap(actionEntry.icon.toBitmap()) + if (icon == null) { + Log.e(TAG, "Icon for action chip is null") + return@forEach + } + + val presentations = constructPresentations( + fillRequest, + index, + actionEntry, + pendingIntent, + icon, + actionEntry.title, + actionEntry.subTitle, + primaryProvider.actionEntryList.size + ) + + fillResponseBuilder.addDataset( + Dataset.Builder() + .setField( + autofillId, + Field.Builder().setPresentations(presentations).build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() + ) + datasetAdded = true + + index++ + } + + return datasetAdded + } + + private fun addCredentialDatasetsForAutofillId( + fillRequest: FillRequest, + autofillId: AutofillId, + providerDataList: List<GetCredentialProviderData>, + entryIconMap: Map<String, Icon>, + fillResponseBuilder: FillResponse.Builder, + typePriorityMap: Map<String, Int>, ): Boolean { val providerList = GetFlowUtils.toProviderList( providerDataList, - this@CredentialAutofillService) + this@CredentialAutofillService + ) if (providerList.isEmpty()) { return false } val providerDisplayInfo: ProviderDisplayInfo = - toProviderDisplayInfo(providerList, typePriorityMap) + toProviderDisplayInfo(providerList, typePriorityMap) var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size - val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest - val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs - val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 - val maxDatasetDisplayLimit = this.resources.getInteger( - com.android.credentialmanager.R.integer.autofill_max_visible_datasets) - .coerceAtMost(totalEntryCount) + var i = 0 var datasetAdded = false @@ -260,8 +345,6 @@ class CredentialAutofillService : AutofillService() { } } } - bottomSheetIntent.putExtra( - ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList) providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{ val primaryEntry = it.sortedCredentialEntryList.first() val pendingIntent = primaryEntry.pendingIntent @@ -271,7 +354,7 @@ class CredentialAutofillService : AutofillService() { Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop } - if (i >= maxDatasetDisplayLimit) { + if (i >= maxDatasetDisplayLimit(totalEntryCount)) { return@usernameLoop } val icon: Icon = if (primaryEntry.icon == null) { @@ -280,116 +363,172 @@ class CredentialAutofillService : AutofillService() { getDefaultIcon() } else { entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] - ?: getDefaultIcon() - } - // Create inline presentation - var inlinePresentation: InlinePresentation? = null - if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) { - val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) { - inlinePresentationSpecs[i] - } else { - inlinePresentationSpecs[inlinePresentationSpecsCount - 1] - } - if (spec != null) { - inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon, - InlinePresentationsFactory.modifyInlinePresentationSpec - (this@CredentialAutofillService, spec), - duplicateDisplayNamesForPasskeys) - } - } - var dropdownPresentation: RemoteViews? = null - if (i < maxDatasetDisplayLimit) { - dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( - this, icon, primaryEntry, /*isFirstEntry= */ i == 0, - /*isLastEntry= */ (totalEntryCount - i == 1)) + ?: getDefaultIcon() } - - val dataSetBuilder = Dataset.Builder() - val presentationBuilder = Presentations.Builder() - if (dropdownPresentation != null) { - presentationBuilder.setMenuPresentation(dropdownPresentation) + val displayName = primaryEntry.displayName + val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY && + displayName != null + ) { + displayName + } else { + primaryEntry.userName } - if (inlinePresentation != null) { - presentationBuilder.setInlinePresentation(inlinePresentation) + val subtitle = if (primaryEntry.credentialType == + CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true + ) { + primaryEntry.userName + } else { + null } - + val presentations = + constructPresentations( + fillRequest, i, primaryEntry, pendingIntent, + icon, title, subtitle, totalEntryCount + ) fillResponseBuilder.addDataset( - dataSetBuilder - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build()) - .build()) - .setAuthentication(pendingIntent.intentSender) - .setCredentialFillInIntent(fillInIntent) - .build()) + Dataset.Builder() + .setField( + autofillId, + Field.Builder().setPresentations( + presentations + ) + .build() + ) + .setAuthentication(pendingIntent.intentSender) + .setCredentialFillInIntent(fillInIntent) + .build() + ) datasetAdded = true i++ } - val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs, - inlinePresentationSpecsCount) - if (datasetAdded) { - addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder) - if (pinnedSpec != null) { - addPinnedInlineSuggestion(pinnedSpec, autofillId, - fillResponseBuilder, bottomSheetIntent) + return datasetAdded + } + + private fun addMoreOptionsDataset( + fillRequest: FillRequest, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder, + bottomSheetIntent: Intent + ) { + val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + val pinnedSpec = getLastInlinePresentationSpec( + inlinePresentationSpecs, + inlinePresentationSpecsCount + ) + addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder) + if (pinnedSpec != null) { + addPinnedInlineSuggestion( + pinnedSpec, autofillId, + fillResponseBuilder, bottomSheetIntent + ) + } + } + + private fun constructPresentations( + fillRequest: FillRequest, + index: Int, + entry: EntryInfo, + pendingIntent: PendingIntent, + icon: Icon, + title: String, + subtitle: String?, + totalEntryCount: Int + ): Presentations { + val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + + // Create inline presentation + var inlinePresentation: InlinePresentation? = null + if (inlinePresentationSpecs != null && index < maxDatasetDisplayLimit(totalEntryCount)) { + val spec: InlinePresentationSpec? = if (index < inlinePresentationSpecsCount) { + inlinePresentationSpecs[index] + } else { + inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + if (spec != null) { + inlinePresentation = createInlinePresentation( + pendingIntent, icon, + InlinePresentationsFactory.modifyInlinePresentationSpec + (this@CredentialAutofillService, spec), + title, subtitle, entry is ActionEntryInfo + ) } } - return datasetAdded + var dropdownPresentation: RemoteViews? = null + if (index < maxDatasetDisplayLimit(totalEntryCount)) { + dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( + this, icon, entry, /*isFirstEntry= */ index == 0, + /*isLastEntry= */ (totalEntryCount - index == 1) + ) + } + + val presentationBuilder = Presentations.Builder() + if (dropdownPresentation != null) { + presentationBuilder.setMenuPresentation(dropdownPresentation) + } + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } + return presentationBuilder.build() } + private fun maxDatasetDisplayLimit(totalEntryCount: Int) = this.resources.getInteger( + com.android.credentialmanager.R.integer.autofill_max_visible_datasets + ).coerceAtMost(totalEntryCount) + private fun createInlinePresentation( - primaryEntry: CredentialEntryInfo, - pendingIntent: PendingIntent, - icon: Icon, - spec: InlinePresentationSpec, - duplicateDisplayNameForPasskeys: MutableMap<String, Boolean> + pendingIntent: PendingIntent, + icon: Icon, + spec: InlinePresentationSpec, + title: String, + subtitle: String?, + isActionEntry: Boolean ): InlinePresentation { - val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY && - primaryEntry.displayName != null) { - primaryEntry.displayName!! - } else { - primaryEntry.userName - } val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setTitle(displayName) + .newContentBuilder(pendingIntent) + .setTitle(title) icon.setTintBlendMode(BlendMode.DST) sliceBuilder.setStartIcon(icon) - if (primaryEntry.credentialType == - CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) { - sliceBuilder.setSubtitle(primaryEntry.userName) + if (subtitle != null && !isActionEntry) { + sliceBuilder.setSubtitle(subtitle) } return InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ false) + sliceBuilder.build().slice, spec, /* pinned= */ false + ) } private fun addDropdownMoreOptionsPresentation( - bottomSheetIntent: Intent, - autofillId: AutofillId, - fillResponseBuilder: FillResponse.Builder + bottomSheetIntent: Intent, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder ) { val presentationBuilder = Presentations.Builder() - .setMenuPresentation( - RemoteViewsFactory.createMoreSignInOptionsPresentation(this)) + .setMenuPresentation( + RemoteViewsFactory.createMoreSignInOptionsPresentation(this) + ) val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent) fillResponseBuilder.addDataset( - Dataset.Builder() - .setId(AutofillManager.PINNED_DATASET_ID) - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build()) - .build()) - .setAuthentication(pendingIntent.intentSender) + Dataset.Builder() + .setId(AutofillManager.PINNED_DATASET_ID) + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build() + ) .build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() ) } private fun getLastInlinePresentationSpec( - inlinePresentationSpecs: List<InlinePresentationSpec>?, - inlinePresentationSpecsCount: Int + inlinePresentationSpecs: List<InlinePresentationSpec>?, + inlinePresentationSpecsCount: Int ): InlinePresentationSpec? { if (inlinePresentationSpecs != null) { return inlinePresentationSpecs[inlinePresentationSpecsCount - 1] @@ -398,40 +537,47 @@ class CredentialAutofillService : AutofillService() { } private fun addPinnedInlineSuggestion( - spec: InlinePresentationSpec, - autofillId: AutofillId, - fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent + spec: InlinePresentationSpec, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder, + bottomSheetIntent: Intent ) { val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent) val dataSetBuilder = Dataset.Builder() val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setStartIcon(Icon.createWithResource(this, - com.android.credentialmanager.R.drawable.more_horiz_24px)) + .newContentBuilder(pendingIntent) + .setStartIcon( + Icon.createWithResource( + this, + com.android.credentialmanager.R.drawable.more_horiz_24px + ) + ) val presentationBuilder = Presentations.Builder() - .setInlinePresentation(InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ true)) + .setInlinePresentation( + InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ true + ) + ) fillResponseBuilder.addDataset( - dataSetBuilder - .setId(AutofillManager.PINNED_DATASET_ID) - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build() - ).build() - ) - .setAuthentication(pendingIntent.intentSender) - .build() + dataSetBuilder + .setId(AutofillManager.PINNED_DATASET_ID) + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build() + ).build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() ) } private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent { intent.setAction(java.util.UUID.randomUUID().toString()) return PendingIntent.getActivity(this, /*requestCode=*/0, intent, - PendingIntent.FLAG_MUTABLE, /*options=*/null) + PendingIntent.FLAG_MUTABLE, /*options=*/null) } /** @@ -465,17 +611,33 @@ class CredentialAutofillService : AutofillService() { * } */ private fun mapAutofillIdToProviders( - providerList: List<GetCredentialProviderData> + uniqueAutofillIdsForRequest: Set<AutofillId>, + providerList: List<GetCredentialProviderData>, + primaryProviderComponentName: ComponentName? ): Map<AutofillId, ArrayList<GetCredentialProviderData>> { val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> = mutableMapOf() + var primaryProvider: GetCredentialProviderData? = null providerList.forEach { provider -> + if (primaryProviderComponentName != null && Objects.equals(ComponentName + .unflattenFromString(provider + .providerFlattenedComponentName), primaryProviderComponentName)) { + primaryProvider = provider + } val autofillIdToCredentialEntries: MutableMap<AutofillId, ArrayList<Entry>> = mapAutofillIdToCredentialEntries(provider.credentialEntries) autofillIdToCredentialEntries.forEach { (autofillId, entries) -> autofillIdToProviders.getOrPut(autofillId) { ArrayList() } - .add(copyProviderInfo(provider, entries)) + .add(copyProviderInfo(provider, entries)) + } + } + // adds primary provider action entries for autofill IDs without credential entries + uniqueAutofillIdsForRequest.forEach { autofillId -> + if (!autofillIdToProviders.containsKey(autofillId) && primaryProvider != null) { + autofillIdToProviders.put( + autofillId, + ArrayList(listOf(copyProviderInfoForActionsOnly(primaryProvider!!)))) } } return autofillIdToProviders @@ -526,19 +688,35 @@ class CredentialAutofillService : AutofillService() { ) } + private fun copyProviderInfoForActionsOnly( + providerInfo: GetCredentialProviderData, + ): GetCredentialProviderData { + return GetCredentialProviderData( + providerInfo.providerFlattenedComponentName, + emptyList(), + providerInfo.actionChips, + emptyList(), + null + ) + } + override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { TODO("Not yet implemented") } private fun getCredManRequest( - structure: AssistStructure, - sessionId: Int, - requestId: Int, - resultReceiver: ResultReceiver, - responseClientState: Bundle + structure: AssistStructure, + sessionId: Int, + requestId: Int, + resultReceiver: ResultReceiver, + responseClientState: Bundle, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): GetCredentialRequest? { val credentialOptions: MutableList<CredentialOption> = mutableListOf() - traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId) + traverseStructureForRequest( + structure, credentialOptions, responseClientState, + sessionId, uniqueAutofillIdsForRequest + ) if (credentialOptions.isNotEmpty()) { val dataBundle = Bundle() @@ -558,7 +736,8 @@ class CredentialAutofillService : AutofillService() { structure: AssistStructure, cmRequests: MutableList<CredentialOption>, responseClientState: Bundle, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ) { val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf() val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf() @@ -570,7 +749,7 @@ class CredentialAutofillService : AutofillService() { windowNodes.forEach { windowNode: AssistStructure.WindowNode -> traverseNodeForRequest( windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes, - credentialOptionsFromHints, sessionId) + credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest) } } @@ -580,7 +759,8 @@ class CredentialAutofillService : AutofillService() { responseClientState: Bundle, traversedViewNodes: MutableSet<AutofillId>, credentialOptionsFromHints: MutableMap<String, CredentialOption>, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ) { viewNode.autofillId?.let { val domain = viewNode.webDomain @@ -590,7 +770,9 @@ class CredentialAutofillService : AutofillService() { WEBVIEW_REQUESTED_CREDENTIAL_KEY, true) } cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState, - traversedViewNodes, credentialOptionsFromHints, sessionId)) + traversedViewNodes, credentialOptionsFromHints, sessionId, + uniqueAutofillIdsForRequest) + ) traversedViewNodes.add(it) } @@ -600,8 +782,10 @@ class CredentialAutofillService : AutofillService() { } children.forEach { childNode: AssistStructure.ViewNode -> - traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes, - credentialOptionsFromHints, sessionId) + traverseNodeForRequest( + childNode, cmRequests, responseClientState, traversedViewNodes, + credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest + ) } } @@ -611,7 +795,8 @@ class CredentialAutofillService : AutofillService() { responseClientState: Bundle, traversedViewNodes: MutableSet<AutofillId>, credentialOptionsFromHints: MutableMap<String, CredentialOption>, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): MutableList<CredentialOption> { val credentialOptions: MutableList<CredentialOption> = mutableListOf() if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) { @@ -641,8 +826,9 @@ class CredentialAutofillService : AutofillService() { CredentialProviderService.EXTRA_AUTOFILL_ID, associatedAutofillIds ) + uniqueAutofillIdsForRequest.addAll(associatedAutofillIds) } - } + } } // TODO(b/325502552): clean up cred option logic in autofill hint val credentialHints: MutableList<String> = mutableListOf() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt index 7bb08d2c26e8..98e1690ace92 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -21,8 +21,9 @@ import android.util.Log import com.android.credentialmanager.common.Constants import android.widget.RemoteViews import androidx.core.content.ContextCompat +import com.android.credentialmanager.model.get.ActionEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo -import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.EntryInfo import android.graphics.drawable.Icon class RemoteViewsFactory { @@ -39,37 +40,47 @@ class RemoteViewsFactory { fun createDropdownPresentation( context: Context, icon: Icon, - credentialEntryInfo: CredentialEntryInfo, + entryInfo: EntryInfo, isFirstEntry: Boolean, isLastEntry: Boolean, ): RemoteViews { var layoutId: Int = com.android.credentialmanager.R.layout .credman_dropdown_presentation_layout val remoteViews = RemoteViews(context.packageName, layoutId) - val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName - remoteViews.setTextViewText(android.R.id.text1, displayName) - val secondaryText = getSecondaryText(credentialEntryInfo) - if (secondaryText.isNullOrBlank()) { - Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null") - } else { - remoteViews.setTextViewText(android.R.id.text2, secondaryText) + if (entryInfo is CredentialEntryInfo) { + val displayName = entryInfo.displayName ?: entryInfo.userName + remoteViews.setTextViewText(android.R.id.text1, displayName) + val secondaryText = getSecondaryText(entryInfo) + if (secondaryText.isNullOrBlank()) { + Log.w(Constants.LOG_TAG, "Secondary text for dropdown credential entry is null") + } else { + remoteViews.setTextViewText(android.R.id.text2, secondaryText) + } + remoteViews.setContentDescription( + android.R.id.icon1, entryInfo + .providerDisplayName + ) + } else if (entryInfo is ActionEntryInfo) { + remoteViews.setTextViewText(android.R.id.text1, entryInfo.title) + remoteViews.setTextViewText(android.R.id.text2, entryInfo.subTitle) } - remoteViews.setImageViewIcon(android.R.id.icon1, icon); + remoteViews.setImageViewIcon(android.R.id.icon1, icon) remoteViews.setBoolean( - android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true); + android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true + ) remoteViews.setInt( android.R.id.icon1, SET_MAX_HEIGHT_METHOD_NAME, context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); - remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo - .providerDisplayName); + com.android.credentialmanager.R.dimen.autofill_icon_size + ) + ) val drawableId = if (isFirstEntry) com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle remoteViews.setInt( - android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId); + android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId) if (isFirstEntry) remoteViews.setViewPadding( com.android.credentialmanager.R.id.credential_card, /* left=*/0, @@ -94,8 +105,8 @@ class RemoteViewsFactory { * providerDisplayName. Both credential type and provider display name should not be empty. */ private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? { - return listOf(if (credentialEntryInfo.displayName != null - && (credentialEntryInfo.displayName != credentialEntryInfo.userName)) + return listOf(if (credentialEntryInfo.displayName != null && + (credentialEntryInfo.displayName != credentialEntryInfo.userName)) (credentialEntryInfo.userName) else null, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() } @@ -113,16 +124,16 @@ class RemoteViewsFactory { com.android.credentialmanager .R.string.dropdown_presentation_more_sign_in_options_text)) remoteViews.setBoolean( - android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true); + android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true) remoteViews.setInt( android.R.id.icon1, SET_MAX_HEIGHT_METHOD_NAME, context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); + com.android.credentialmanager.R.dimen.autofill_icon_size)) val drawableId = com.android.credentialmanager.R.drawable.more_options_list_item remoteViews.setInt( - android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId); + android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId) return remoteViews } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 8e7886119a34..19f5a99f46fa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -292,12 +292,12 @@ private fun toGetScreenState( providerDisplayInfo.remoteEntry == null && providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty }) GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY + else if (isRequestForAllOptions) + GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() && providerDisplayInfo.authenticationEntryList.isEmpty() && providerDisplayInfo.remoteEntry != null) GetScreenState.REMOTE_ONLY - else if (isRequestForAllOptions) - GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 30bec7724dd5..c2a83b1e772f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -68,6 +68,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -1442,14 +1443,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> int stringRes = R.string.bluetooth_pairing; //when profile is connected, information would be available if (profileConnected) { - // Update Meta data for connected device - if (BluetoothUtils.getBooleanMetaData( - mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { - leftBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); - rightBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); - } + leftBattery = getLeftBatteryLevel(); + rightBattery = getRightBatteryLevel(); // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { @@ -1485,7 +1480,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio && isConnectedHapClientDevice(); if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) { - return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary); + stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary); } } } @@ -1498,6 +1493,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level || stringRes == R.string.bluetooth_active_battery_level || stringRes == R.string.bluetooth_active_battery_level_untethered + || stringRes == R.string.bluetooth_active_battery_level_untethered_left + || stringRes == R.string.bluetooth_active_battery_level_untethered_right || stringRes == R.string.bluetooth_battery_level_untethered; if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) { return getTvBatterySummary( @@ -1510,6 +1507,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (isTwsBatteryAvailable(leftBattery, rightBattery)) { return mContext.getString(stringRes, Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); + } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(leftBattery)); + } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(rightBattery)); } else { return mContext.getString(stringRes, batteryLevelPercentageString); } @@ -1553,60 +1558,34 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return spannableBuilder; } - private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery, + private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery, boolean shortSummary) { + boolean isLeftDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent(); + boolean isRightDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent(); + boolean shouldShowLeftBattery = + !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + boolean shouldShowRightBattery = + !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); - CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter( - CachedBluetoothDevice::isConnected).findFirst().orElse(null); - if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) { - memberDevice = mSubDevice; - } - - CachedBluetoothDevice leftDevice = null; - CachedBluetoothDevice rightDevice = null; - final int deviceSide = getDeviceSide(); - if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) { - leftDevice = this; - rightDevice = memberDevice; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) { - leftDevice = memberDevice; - rightDevice = this; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { - leftDevice = this; - rightDevice = this; + if (isLeftDeviceConnected && isRightDeviceConnected) { + return (shouldShowLeftBattery && shouldShowRightBattery) + ? R.string.bluetooth_active_battery_level_untethered + : R.string.bluetooth_hearing_aid_left_and_right_active; } - - if (leftBattery < 0 && leftDevice != null) { - leftBattery = leftDevice.getBatteryLevel(); + if (isLeftDeviceConnected) { + return shouldShowLeftBattery + ? R.string.bluetooth_active_battery_level_untethered_left + : R.string.bluetooth_hearing_aid_left_active; } - if (rightBattery < 0 && rightDevice != null) { - rightBattery = rightDevice.getBatteryLevel(); - } - - if (leftDevice != null && rightDevice != null) { - if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered, - Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active); - } - } else if (leftDevice != null) { - if (leftBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left, - Utils.formatPercentage(leftBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_active); - } - } else if (rightDevice != null) { - if (rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right, - Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_right_active); - } + if (isRightDeviceConnected) { + return shouldShowRightBattery + ? R.string.bluetooth_active_battery_level_untethered_right + : R.string.bluetooth_hearing_aid_right_active; } - return mContext.getString(R.string.bluetooth_active_no_battery_level); + return R.string.bluetooth_active_no_battery_level; } private void addBatterySpan(SpannableStringBuilder builder, @@ -1632,6 +1611,56 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return leftBattery >= 0 && rightBattery >= 0; } + private Optional<CachedBluetoothDevice> getConnectedHearingAidSide( + @HearingAidInfo.DeviceSide int side) { + return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream()) + .filter(Objects::nonNull) + .filter(device -> device.getDeviceSide() == side + || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) + .filter(device -> device.getDevice().isConnected()) + // For hearing aids, we should expect only one device assign to one side, but if + // it happens, we don't care which one. + .findAny(); + } + + private int getLeftBatteryLevel() { + int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + leftBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return leftBattery; + } + + private int getRightBatteryLevel() { + int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData( + mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + rightBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return rightBattery; + } + private boolean isProfileConnectedFail() { Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress() + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index b4bd4826ea08..b9bf9caddac7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -753,33 +753,71 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); + } + + @Test + public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); + } + + @Test + public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); } @Test @@ -851,35 +889,45 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10%, R: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary().toString()) + .isEqualTo("Active. L: 10%, R: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery Right: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()) + .isEqualTo("Left: 10% battery Right: 10% battery"); } @Test @@ -1151,7 +1199,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1160,7 +1208,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( - "Active, L: 15% battery, R: 25% battery"); + "Active. L: 15%, R: 25% battery."); } @Test @@ -1169,7 +1217,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1178,7 +1226,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left 15% Right 25%"); + "Left: 15% battery Right: 25% battery"); } @Test @@ -1741,16 +1789,6 @@ public class CachedBluetoothDeviceTest { BluetoothProfile.STATE_CONNECTED); } - private void updateProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mDevice); - mCachedDevice.onProfileStateChanged(profile, status); - } - - private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mSubDevice); - mSubCachedDevice.onProfileStateChanged(profile, status); - } - @Test public void getSubDevice_setSubDevice() { mCachedDevice.setSubDevice(mSubCachedDevice); @@ -2030,6 +2068,29 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.getConnectionSummary(false)).isNull(); } + private void updateProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mDevice); + mCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mCachedDevice); + } + + private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mSubDevice); + mSubCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mSubCachedDevice); + } + + private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) { + for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) { + int status = cachedBluetoothDevice.getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true); + return; + } + } + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false); + } + private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 14ebc3907c04..755fe2a4476a 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -4,6 +4,16 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { + name: "delay_show_magnification_button" + namespace: "accessibility" + description: "Delays the showing of magnification mode switch button." + bug: "338259519" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "floating_menu_animated_tuck" namespace: "accessibility" description: "Sets up animations for tucking/untucking and adjusts clipbounds." diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 80398cd2544d..e69ac0a555b6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -857,6 +857,16 @@ flag { } flag { + name: "restart_dream_on_unocclude" + namespace: "systemui" + description: "re-enters dreaming upon unocclude when dreaming when originally occluding" + bug: "338051457" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "communal_bouncer_do_not_modify_plugin_open" namespace: "systemui" description: "do not modify notification shade when handling bouncer expansion." @@ -865,3 +875,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "app_clips_backlinks" + namespace: "systemui" + description: "Enables Backlinks improvement feature in App Clips" + bug: "300307759" +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 3227611b841d..6fe5cef8b1e3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -59,7 +59,8 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets @@ -277,7 +278,6 @@ fun CommunalHub( if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) { Toolbar( - isDraggingToRemove = isDraggingToRemove, setToolbarSize = { toolbarSize = it }, setRemoveButtonCoordinates = { removeButtonCoordinates = it }, onEditDone = onEditDone, @@ -577,7 +577,6 @@ private fun LockStateIcon( */ @Composable private fun Toolbar( - isDraggingToRemove: Boolean, removeEnabled: Boolean, onRemoveClicked: () -> Unit, setToolbarSize: (toolbarSize: IntSize) -> Unit, @@ -591,7 +590,7 @@ private fun Toolbar( label = "RemoveButtonAlphaAnimation" ) - Row( + Box( modifier = Modifier.fillMaxWidth() .padding( @@ -600,65 +599,54 @@ private fun Toolbar( end = Dimensions.ToolbarPaddingHorizontal, ) .onSizeChanged { setToolbarSize(it) }, - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically ) { val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing) - Button( - onClick = onOpenWidgetPicker, - colors = filledButtonColors(), - contentPadding = Dimensions.ButtonPadding - ) { - Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) - Spacer(spacerModifier) - Text( - text = stringResource(R.string.hub_mode_add_widget_button_text), - ) - } - val colors = LocalAndroidColorScheme.current - if (isDraggingToRemove) { + if (!removeEnabled) { Button( - // Button is disabled to make it non-clickable - enabled = false, - onClick = {}, - colors = - ButtonDefaults.buttonColors( - disabledContainerColor = colors.primary, - disabledContentColor = colors.onPrimary, - ), - contentPadding = Dimensions.ButtonPadding, - modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) } + modifier = Modifier.align(Alignment.CenterStart), + onClick = onOpenWidgetPicker, + colors = filledButtonColors(), + contentPadding = Dimensions.ButtonPadding ) { - RemoveButtonContent(spacerModifier) + Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_add_widget_button_text), + ) } - } else { - OutlinedButton( - enabled = removeEnabled, + } + + if (removeEnabled) { + Button( onClick = onRemoveClicked, - colors = - ButtonDefaults.outlinedButtonColors( - contentColor = colors.primary, - disabledContentColor = colors.primary - ), - border = BorderStroke(width = 1.0.dp, color = colors.primary), + colors = filledButtonColors(), contentPadding = Dimensions.ButtonPadding, modifier = Modifier.graphicsLayer { alpha = removeButtonAlpha } .onGloballyPositioned { setRemoveButtonCoordinates(it) } + .align(Alignment.Center) ) { RemoveButtonContent(spacerModifier) } } - Button( - onClick = onEditDone, - colors = filledButtonColors(), - contentPadding = Dimensions.ButtonPadding - ) { - Text( - text = stringResource(R.string.hub_mode_editing_exit_button_text), - ) + if (!removeEnabled) { + Button( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = onEditDone, + colors = filledButtonColors(), + contentPadding = Dimensions.ButtonPadding + ) { + Icon( + Icons.Default.Check, + stringResource(id = R.string.hub_mode_editing_exit_button_text) + ) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_editing_exit_button_text), + ) + } } } } @@ -762,7 +750,7 @@ private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) { @Composable private fun RemoveButtonContent(spacerModifier: Modifier) { - Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget)) + Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) Spacer(spacerModifier) Text( text = stringResource(R.string.button_to_remove_widget), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 5f84dd47a240..2f241cec37ee 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -181,6 +181,7 @@ fun FooterActions( val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding) Row( modifier + .sysuiResTag("qs_footer_actions") .fillMaxWidth() .graphicsLayer { this.alpha = alpha } .then(backgroundModifier) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index bca8fde17fce..c5d43fd85001 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -68,6 +69,8 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation +import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton @@ -152,6 +155,8 @@ private fun SceneScope.QuickSettingsScene( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() val contentAlpha by animateFloatAsState( @@ -183,6 +188,9 @@ private fun SceneScope.QuickSettingsScene( // scene (and not the one under it) during a scene transition. Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) } + .thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + }, ) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() @@ -280,7 +288,8 @@ private fun SceneScope.QuickSettingsScene( } Column( - modifier = shadeHeaderAndQuickSettingsModifier, + modifier = + shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"), ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> @@ -328,7 +337,7 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = false, - modifier = Modifier.sysuiResTag("expanded_qs_scroll_view") + modifier = Modifier.sysuiResTag("quick_settings_panel") ) MediaCarousel( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index 36b60d64399a..10fe0cabad53 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -67,6 +68,8 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation +import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.composable.MediaCarousel @@ -208,6 +211,8 @@ private fun SceneScope.SingleShade( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by animateSceneFloatAsState( @@ -243,9 +248,15 @@ private fun SceneScope.SingleShade( Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = - Modifier.fillMaxWidth().thenIf(isClickable) { - Modifier.clickable(onClick = { viewModel.onContentClicked() }) - } + Modifier.fillMaxWidth() + .thenIf(isClickable) { + Modifier.clickable( + onClick = { viewModel.onContentClicked() } + ) + } + .thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + }, ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index 92d5c26148e4..d924d88d1e59 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -74,16 +74,6 @@ sealed interface ObservableTransitionState { */ val isUserInputOngoing: Flow<Boolean>, ) : ObservableTransitionState - - fun isIdle(scene: SceneKey?): Boolean { - return this is Idle && (scene == null || this.currentScene == scene) - } - - fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { - return this is Transition && - (from == null || this.fromScene == from) && - (to == null || this.toScene == to) - } } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt index def63ec5b171..bfed33c54019 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.communal +import android.platform.test.annotations.EnableFlags import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -62,6 +66,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor, keyguardInteractor = kosmos.keyguardInteractor, keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + communalInteractor = kosmos.communalInteractor, dreamManager = dreamManager, bgScope = kosmos.applicationCoroutineScope, ) @@ -84,6 +89,32 @@ class CommunalDreamStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE) + fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() = + testScope.runTest { + keyguardRepository.setDreaming(false) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + runCurrent() + + verify(dreamManager, never()).startDream() + + kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + kosmos.fakeKeyguardRepository.setDreaming(true) + runCurrent() + + transition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED) + kosmos.fakeKeyguardRepository.setKeyguardOccluded(false) + kosmos.fakeKeyguardRepository.setDreaming(false) + runCurrent() + + transition(from = KeyguardState.OCCLUDED, to = KeyguardState.DREAMING) + runCurrent() + + verify(dreamManager).startDream() + } + + @Test fun shouldNotStartDreamWhenIneligibleToDream() = testScope.runTest { keyguardRepository.setDreaming(false) diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a1daebd7513e..5857692cdaa9 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -285,6 +285,9 @@ the amount by the view is positioned above the screen before the animation starts. --> <dimen name="heads_up_appear_y_above_screen">32dp</dimen> + <!-- padding between the old and new heads up notifications for the hun cycling animation --> + <dimen name="heads_up_cycling_padding">8dp</dimen> + <!-- padding between the heads up and the statusbar --> <dimen name="heads_up_status_bar_padding">8dp</dimen> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt index 422f02f0a7c9..8979ef1aa160 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt @@ -16,7 +16,6 @@ package com.android.systemui.biometrics import android.Manifest -import android.annotation.IntDef import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX @@ -39,14 +38,9 @@ import android.view.WindowMetrics import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager import com.android.internal.widget.LockPatternUtils -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy +import com.android.systemui.biometrics.shared.model.PromptKind object Utils { - const val CREDENTIAL_PIN = 1 - const val CREDENTIAL_PATTERN = 2 - const val CREDENTIAL_PASSWORD = 3 - /** Base set of layout flags for fingerprint overlay widgets. */ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or @@ -91,17 +85,16 @@ object Utils { (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0 @JvmStatic - @CredentialType - fun getCredentialType(utils: LockPatternUtils, userId: Int): Int = + fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind = when (utils.getKeyguardStoredPasswordQuality(userId)) { - PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN + PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern PASSWORD_QUALITY_NUMERIC, - PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN + PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin PASSWORD_QUALITY_ALPHABETIC, PASSWORD_QUALITY_ALPHANUMERIC, PASSWORD_QUALITY_COMPLEX, - PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD - else -> CREDENTIAL_PASSWORD + PASSWORD_QUALITY_MANAGED -> PromptKind.Password + else -> PromptKind.Password } @JvmStatic @@ -129,8 +122,4 @@ object Utils { return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars()) ?: Insets.NONE } - - @Retention(RetentionPolicy.SOURCE) - @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD) - annotation class CredentialType } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index a97e2dc42607..87829621f57d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * 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. @@ -16,16 +16,20 @@ package com.android.systemui.biometrics.shared.model -import com.android.systemui.biometrics.Utils - -// TODO(b/251476085): this should eventually replace Utils.CredentialType -/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */ sealed interface PromptKind { + object None : PromptKind + data class Biometric( val activeModalities: BiometricModalities = BiometricModalities(), + // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of + // simply depending on rotations. + val showTwoPane: Boolean = false ) : PromptKind object Pin : PromptKind object Pattern : PromptKind object Password : PromptKind + + fun isBiometric() = this is Biometric + fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password) } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 177d933f2d9f..35c202437ed7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -30,6 +30,8 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; @@ -41,6 +43,8 @@ import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.window.InputTransferToken; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.CoreStartable; @@ -69,6 +73,9 @@ import javax.inject.Inject; public class Magnification implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = "Magnification"; + @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300; + private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1; + private final ModeSwitchesController mModeSwitchesController; private final Context mContext; private final Handler mHandler; @@ -209,8 +216,26 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { SysUiState sysUiState, OverviewProxyService overviewProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger) { + this(context, mainHandler.getLooper(), executor, commandQueue, + modeSwitchesController, sysUiState, overviewProxyService, secureSettings, + displayTracker, displayManager, a11yLogger); + } + + @VisibleForTesting + public Magnification(Context context, Looper looper, @Main Executor executor, + CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, + SysUiState sysUiState, OverviewProxyService overviewProxyService, + SecureSettings secureSettings, DisplayTracker displayTracker, + DisplayManager displayManager, AccessibilityLogger a11yLogger) { mContext = context; - mHandler = mainHandler; + mHandler = new Handler(looper) { + @Override + public void handleMessage(@NonNull Message msg) { + if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) { + showMagnificationButtonInternal(msg.arg1, msg.arg2); + } + } + }; mExecutor = executor; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; @@ -350,6 +375,21 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void showMagnificationButton(int displayId, int magnificationMode) { + if (Flags.delayShowMagnificationButton()) { + if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { + return; + } + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), + DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); + } else { + showMagnificationButtonInternal(displayId, magnificationMode); + } + } + + @MainThread + private void showMagnificationButtonInternal(int displayId, int magnificationMode) { // not to show mode switch button if settings panel is already showing to // prevent settings panel be covered by the button. if (isMagnificationSettingsPanelShowing(displayId)) { @@ -360,6 +400,9 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void removeMagnificationButton(int displayId) { + if (Flags.delayShowMagnificationButton()) { + mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); + } mModeSwitchesController.removeButton(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 5ba0b2df7664..9ba41ef9ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -76,6 +76,7 @@ import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.BiometricModalities; +import com.android.systemui.biometrics.shared.model.PromptKind; import com.android.systemui.biometrics.ui.BiometricPromptLayout; import com.android.systemui.biometrics.ui.CredentialView; import com.android.systemui.biometrics.ui.binder.BiometricViewBinder; @@ -500,24 +501,18 @@ public class AuthContainerView extends LinearLayout private void addCredentialView(boolean animatePanel, boolean animateContents) { final LayoutInflater factory = LayoutInflater.from(mContext); - @Utils.CredentialType final int credentialType = Utils.getCredentialType( - mLockPatternUtils, mEffectiveUserId); - - switch (credentialType) { - case Utils.CREDENTIAL_PATTERN: - mCredentialView = factory.inflate( - R.layout.auth_credential_pattern_view, null, false); - break; - case Utils.CREDENTIAL_PIN: - mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false); - break; - case Utils.CREDENTIAL_PASSWORD: - mCredentialView = factory.inflate( - R.layout.auth_credential_password_view, null, false); - break; - default: - throw new IllegalStateException("Unknown credential type: " + credentialType); + PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId); + final int layoutResourceId; + if (credentialType instanceof PromptKind.Pattern) { + layoutResourceId = R.layout.auth_credential_pattern_view; + } else if (credentialType instanceof PromptKind.Pin) { + layoutResourceId = R.layout.auth_credential_pin_view; + } else if (credentialType instanceof PromptKind.Password) { + layoutResourceId = R.layout.auth_credential_password_view; + } else { + throw new IllegalStateException("Unknown credential type: " + credentialType); } + mCredentialView = factory.inflate(layoutResourceId, null, false); // The background is used for detecting taps / cancelling authentication. Since the // credential view is full-screen and should not be canceled from background taps, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index 9ad3f4313838..f659ff0a4749 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -125,7 +125,7 @@ constructor( private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null) override val userId = _userId.asStateFlow() - private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) + private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None) override val kind = _kind.asStateFlow() private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) @@ -149,7 +149,7 @@ constructor( override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - val hasCredentialViewShown = kind.value !is PromptKind.Biometric + val hasCredentialViewShown = kind.value.isCredential() val showBpForCredential = Flags.customBiometricPrompt() && constraintBp() && @@ -178,7 +178,7 @@ constructor( _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.Biometric() + _kind.value = PromptKind.None _opPackageName.value = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index b7c0fa802db3..f8fb7bb7dff7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -16,10 +16,8 @@ package com.android.systemui.biometrics.domain.interactor -import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest @@ -42,12 +40,6 @@ import kotlinx.coroutines.withContext * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users * PIN, pattern, or password credential instead of a biometric. * - * This is used to cache the calling app's options that were given to the underlying authenticate - * APIs and should be set before any UI is shown to the user. - * - * There can be at most one request active at a given time. Use [resetPrompt] when no request is - * active to clear the cache. - * * Views that use any biometric should use [PromptSelectorInteractor] instead. */ class PromptCredentialInteractor @@ -137,28 +129,6 @@ constructor( private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null) val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow() - /** Update the current request to use credential-based authentication instead of biometrics. */ - fun useCredentialsForAuthentication( - promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, - challenge: Long, - opPackageName: String, - ) { - biometricPromptRepository.setPrompt( - promptInfo, - userId, - challenge, - kind.asBiometricPromptCredential(), - opPackageName, - ) - } - - /** Unset the current authentication request. */ - fun resetPrompt() { - biometricPromptRepository.unsetPrompt() - } - /** * Check a credential and return the attestation token (HAT) if successful. * @@ -231,13 +201,3 @@ constructor( _verificationError.value = null } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 45816c12281e..deb47d309ea5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -18,7 +18,6 @@ package com.android.systemui.biometrics.domain.interactor import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternUtils -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.Utils.getCredentialType import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository @@ -95,7 +94,7 @@ interface PromptSelectorInteractor { /** Use credential-based authentication instead of biometrics. */ fun useCredentialsForAuthentication( promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, + kind: PromptKind, userId: Int, challenge: Long, opPackageName: String, @@ -152,14 +151,7 @@ constructor( override val credentialKind: Flow<PromptKind> = combine(prompt, isCredentialAllowed) { prompt, isAllowed -> if (prompt != null && isAllowed) { - when ( - getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) - ) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } + getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) } else { PromptKind.Biometric() } @@ -191,7 +183,7 @@ constructor( override fun useCredentialsForAuthentication( promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, + kind: PromptKind, userId: Int, challenge: Long, opPackageName: String, @@ -200,7 +192,7 @@ constructor( promptInfo = promptInfo, userId = userId, gatekeeperChallenge = challenge, - kind = kind.asBiometricPromptCredential(), + kind = kind, opPackageName = opPackageName, ) } @@ -209,13 +201,3 @@ constructor( promptRepository.unsetPrompt() } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt index 9e7fb4e73a29..153b7aa3e522 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt @@ -20,6 +20,8 @@ import android.annotation.SuppressLint import android.app.DreamManager import com.android.systemui.CoreStartable import com.android.systemui.Flags.communalHub +import com.android.systemui.Flags.restartDreamOnUnocclude +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -27,8 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -43,6 +47,7 @@ constructor( private val powerInteractor: PowerInteractor, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val communalInteractor: CommunalInteractor, private val dreamManager: DreamManager, @Background private val bgScope: CoroutineScope, ) : CoreStartable { @@ -52,6 +57,19 @@ constructor( return } + // Return to dream from occluded when not already dreaming. + if (restartDreamOnUnocclude()) { + keyguardTransitionInteractor.startedKeyguardTransitionStep + .sample(keyguardInteractor.isDreaming, ::Pair) + .filter { + it.first.from == KeyguardState.OCCLUDED && + it.first.to == KeyguardState.DREAMING && + !it.second + } + .onEach { dreamManager.startDream() } + .launchIn(bgScope) + } + // Restart the dream underneath the hub in order to support the ability to swipe // away the hub to enter the dream. keyguardTransitionInteractor.finishedKeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 6b4cf79d59b1..06c83962df6b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -163,6 +163,13 @@ constructor( initialValue = false, ) + /** Whether to start dreaming when returning from occluded */ + val dreamFromOccluded: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsToState(KeyguardState.OCCLUDED) + .map { it.from == KeyguardState.DREAMING } + .stateIn(scope = applicationScope, SharingStarted.Eagerly, false) + /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. * diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java index 0fd688760a32..8c3de4bd1928 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java @@ -89,8 +89,8 @@ public abstract class DozeModule { dozeFalsingManagerAdapter, dozeTriggers, dozeUi, - dozeScreenState, dozeScreenBrightness, + dozeScreenState, dozeWallpaperState, dozeDockHandler, dozeAuthRemover, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 9b07675f672c..5a28f7113ebd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -26,7 +26,6 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.wm.shell.animation.Interpolators import javax.inject.Inject @@ -141,8 +140,6 @@ constructor( } private fun listenForAlternateBouncerToGone() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // Handled via #dismissAlternateBouncer. return @@ -165,8 +162,6 @@ constructor( } private fun listenForAlternateBouncerToPrimaryBouncer() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index a306954b7d8f..4d737749fbc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion. import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -186,7 +185,6 @@ constructor( * PRIMARY_BOUNCER. */ private fun listenForAodToPrimaryBouncer() { - if (SceneContainerFlag.isEnabled) return scope.launch("$TAG#listenForAodToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index 63294f7609a2..e738ea4397de 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -26,7 +26,6 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -94,8 +93,6 @@ constructor( } private fun listenForDreamingLockscreenHostedToPrimaryBouncer() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } @@ -104,8 +101,6 @@ constructor( } private fun listenForDreamingLockscreenHostedToGone() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 7961b45830d4..c952e0879d12 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,7 +29,6 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -89,8 +88,6 @@ constructor( private fun listenForDreamingToGlanceableHub() { if (!communalHub()) return - if (SceneContainerFlag.isEnabled) - return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { glanceableHubTransitions.listenForGlanceableHubTransition( transitionOwnerName = TAG, @@ -178,8 +175,6 @@ constructor( } private fun listenForDreamingToGoneWhenDismissable() { - if (SceneContainerFlag.isEnabled) - return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch { keyguardInteractor.isAbleToDream .sampleCombine( @@ -195,8 +190,6 @@ constructor( } private fun listenForDreamingToGoneFromBiometricUnlock() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index da4e989d25a5..faab033441c1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject @@ -63,8 +62,6 @@ constructor( ) { override fun start() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return if (!Flags.communalHub()) { return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 2b3732f75812..c2c095bb9574 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -29,7 +29,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -63,8 +62,6 @@ constructor( ) { override fun start() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return listenForGoneToAodOrDozing() listenForGoneToDreaming() listenForGoneToLockscreenOrHub() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index dad2d9692dbc..56261e0865e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -20,7 +20,6 @@ import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch -import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -33,7 +32,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import java.util.UUID @@ -152,7 +150,6 @@ constructor( } private fun listenForLockscreenToPrimaryBouncer() { - if (SceneContainerFlag.isEnabled) return scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } @@ -177,7 +174,6 @@ constructor( /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ private fun listenForLockscreenToPrimaryBouncerDragging() { - if (SceneContainerFlag.isEnabled) return var transitionId: UUID? = null scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") { shadeRepository.legacyShadeExpansion @@ -284,7 +280,6 @@ constructor( } private fun listenForLockscreenToGoneDragging() { - if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // When the refactor is enabled, we no longer use isKeyguardGoingAway. scope.launch("$TAG#listenForLockscreenToGoneDragging") { @@ -342,9 +337,7 @@ constructor( * keyguard transition. */ private fun listenForLockscreenToGlanceableHub() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return - if (!Flags.communalHub()) { + if (!com.android.systemui.Flags.communalHub()) { return } scope.launch(mainDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 95592506251f..2a7178f5e24f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -26,7 +27,6 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -90,10 +90,21 @@ constructor( .filterRelevantKeyguardStateAnd { onTop -> !onTop } .sample( communalInteractor.isIdleOnCommunal, - communalInteractor.showCommunalFromOccluded + communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded ) - .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) -> - startTransitionToLockscreenOrHub(isIdleOnCommunal, showCommunalFromOccluded) + .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) -> + // Occlusion signals come from the framework, and should interrupt any + // existing transition + val to = + if (restartDreamOnUnocclude() && dreamFromOccluded) { + KeyguardState.DREAMING + } else if (isIdleOnCommunal || showCommunalFromOccluded) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to) } } } else { @@ -103,33 +114,30 @@ constructor( keyguardInteractor.isKeyguardShowing, communalInteractor.isIdleOnCommunal, communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) -> + .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) -> !isOccluded && isShowing } - .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) -> - startTransitionToLockscreenOrHub(isIdleOnCommunal, showCommunalFromOccluded) + .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) + -> + // Occlusion signals come from the framework, and should interrupt any + // existing transition + val to = + if (restartDreamOnUnocclude() && dreamFromOccluded) { + KeyguardState.DREAMING + } else if (isIdleOnCommunal || showCommunalFromOccluded) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to) } } } } - private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub( - isIdleOnCommunal: Boolean, - showCommunalFromOccluded: Boolean, - ) { - if (isIdleOnCommunal || showCommunalFromOccluded) { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return - startTransitionTo(KeyguardState.GLANCEABLE_HUB) - } else { - startTransitionTo(KeyguardState.LOCKSCREEN) - } - } - private fun listenForOccludedToGone() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // We don't think OCCLUDED to GONE is possible. You should always have to go via a // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard @@ -150,6 +158,10 @@ constructor( } } + fun dismissToGone() { + scope.launch { startTransitionTo(KeyguardState.GONE) } + } + private fun listenForOccludedToAsleep() { scope.launch { listenForSleepTransition() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 53a0c3200f3d..181a551b0537 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample @@ -99,8 +98,6 @@ constructor( } private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { scope.launch { keyguardInteractor.primaryBouncerShowing @@ -161,14 +158,10 @@ constructor( } private fun listenForPrimaryBouncerToAsleep() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { listenForSleepTransition() } } private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair) @@ -181,8 +174,6 @@ constructor( } private fun listenForPrimaryBouncerToGone() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // This is handled in KeyguardSecurityContainerController and // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index fcf67d519cae..197221a7b5b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -25,7 +25,6 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject @@ -50,8 +49,6 @@ constructor( fromState: KeyguardState, toState: KeyguardState, ) { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return val toScene = if (fromState == KeyguardState.GLANCEABLE_HUB) { CommunalScenes.Blank diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 2c05d49f8040..a18579d9c8e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -28,11 +28,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -356,8 +356,6 @@ constructor( * state. */ fun startDismissKeyguardTransition() { - // TODO(b/336576536): Check if adaptation for scene framework is needed - if (SceneContainerFlag.isEnabled) return when (val startedState = startedKeyguardState.replayCache.last()) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index dc35e4311d25..bb2eeb77969d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -16,16 +16,11 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor -import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -47,7 +42,6 @@ constructor( fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, - sceneInteractor: SceneInteractor, ) { private val defaultSurfaceBehindVisibility = transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible) @@ -109,42 +103,21 @@ constructor( * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it. */ val usingKeyguardGoingAwayAnimation: Flow<Boolean> = - if (SceneContainerFlag.isEnabled) { - combine( - sceneInteractor.transitionState, - surfaceBehindInteractor.isAnimatingSurface, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning, - ) { transition, isAnimatingSurface, isLaunchAnimationRunning -> - // Using the animation if we're animating it directly, or if the - // ActivityLaunchAnimator is in the process of animating it. - val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning - // We may still be animating the surface after the keyguard is fully GONE, since - // some animations (like the translation spring) are not tied directly to the - // transition step amount. - transition.isTransitioning(to = Scenes.Gone) || - (isAnyAnimationRunning && - (transition.isIdle(Scenes.Gone) || - transition.isTransitioning(from = Scenes.Gone))) - } - .distinctUntilChanged() - } else { - combine( - transitionInteractor.isInTransitionToState(KeyguardState.GONE), - transitionInteractor.finishedKeyguardState, - surfaceBehindInteractor.isAnimatingSurface, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning, - ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> - // Using the animation if we're animating it directly, or if the - // ActivityLaunchAnimator is in the process of animating it. - val animationsRunning = isAnimatingSurface || notifLaunchRunning - // We may still be animating the surface after the keyguard is fully GONE, since - // some animations (like the translation spring) are not tied directly to the - // transition step amount. - isInTransitionToGone || - (finishedState == KeyguardState.GONE && animationsRunning) - } - .distinctUntilChanged() - } + combine( + transitionInteractor.isInTransitionToState(KeyguardState.GONE), + transitionInteractor.finishedKeyguardState, + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val animationsRunning = isAnimatingSurface || notifLaunchRunning + // We may still be animating the surface after the keyguard is fully GONE, since + // some animations (like the translation spring) are not tied directly to the + // transition step amount. + isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning) + } + .distinctUntilChanged() /** * Whether the lockscreen is visible, from the Window Manager (WM) perspective. @@ -154,44 +127,28 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = - if (SceneContainerFlag.isEnabled) { - sceneInteractor.transitionState - .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen)) - .map { (prevTransitionState, transitionState) -> - val isReturningToGoneAfterCancellation = - prevTransitionState.isTransitioning(from = Scenes.Gone) && - transitionState.isTransitioning(to = Scenes.Gone) - val isNotOnGone = - !transitionState.isTransitioning(from = Scenes.Gone) && - !transitionState.isIdle(Scenes.Gone) + transitionInteractor.currentKeyguardState + .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) + .map { (currentState, startedWithPrev) -> + val startedFromStep = startedWithPrev?.previousValue + val startedStep = startedWithPrev?.newValue + val returningToGoneAfterCancellation = + startedStep?.to == KeyguardState.GONE && + startedFromStep?.transitionState == TransitionState.CANCELED && + startedFromStep.from == KeyguardState.GONE - isNotOnGone && !isReturningToGoneAfterCancellation - } - .distinctUntilChanged() - } else { - transitionInteractor.currentKeyguardState - .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) - .map { (currentState, startedWithPrev) -> - val startedFromStep = startedWithPrev?.previousValue - val startedStep = startedWithPrev?.newValue - val returningToGoneAfterCancellation = - startedStep?.to == KeyguardState.GONE && - startedFromStep?.transitionState == TransitionState.CANCELED && - startedFromStep.from == KeyguardState.GONE - - if (!returningToGoneAfterCancellation) { - // By default, apply the lockscreen visibility of the current state. - KeyguardState.lockscreenVisibleInState(currentState) - } else { - // If we're transitioning to GONE after a prior canceled transition from - // GONE, then this is the camera launch transition from an asleep state back - // to GONE. We don't want to show the lockscreen since we're aborting the - // lock and going back to GONE. - KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) - } + if (!returningToGoneAfterCancellation) { + // By default, apply the lockscreen visibility of the current state. + KeyguardState.lockscreenVisibleInState(currentState) + } else { + // If we're transitioning to GONE after a prior canceled transition from GONE, + // then this is the camera launch transition from an asleep state back to GONE. + // We don't want to show the lockscreen since we're aborting the lock and going + // back to GONE. + KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) } - .distinctUntilChanged() - } + } + .distinctUntilChanged() /** * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 6fb5174cc612..5720f7603ab2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -125,7 +125,10 @@ public class PageIndicator extends ViewGroup { public void setNumPages(int numPages) { setVisibility(numPages > 1 ? View.VISIBLE : View.GONE); - if (numPages == getChildCount()) { + int childCount = getChildCount(); + // We're checking if the width needs to be updated as it's possible that the number of pages + // was changed while the page indicator was not visible, automatically skipping onMeasure. + if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) { return; } if (mAnimating) { @@ -295,6 +298,10 @@ public class PageIndicator extends ViewGroup { } } + private int calculateWidth(int numPages) { + return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int N = getChildCount(); @@ -309,7 +316,7 @@ public class PageIndicator extends ViewGroup { for (int i = 0; i < N; i++) { getChildAt(i).measure(widthChildSpec, heightChildSpec); } - int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth; + int width = calculateWidth(N); setMeasuredDimension(width, mPageIndicatorHeight); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 6539cf35b073..86cc6f522dd9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -23,10 +23,12 @@ import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -71,7 +73,7 @@ import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp -import com.android.compose.modifiers.background +import com.android.compose.animation.Expandable import com.android.compose.theme.colorAttr import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon @@ -134,7 +136,7 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic } } - @OptIn(ExperimentalCoroutinesApi::class) + @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) @Composable private fun Tile( tile: TileViewModel, @@ -147,28 +149,39 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic .collectAsState(initial = tile.currentState.toUiState()) val context = LocalContext.current - Row( - modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(iconOnly) + Expandable( + color = colorAttr(state.colors.background), + shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)), ) { - val icon = - remember(state.icon) { - state.icon.get().let { - if (it is QSTileImpl.ResourceIcon) { - Icon.Resource(it.resId, null) - } else { - Icon.Loaded(it.getDrawable(context), null) + Row( + modifier = + modifier + .combinedClickable( + onClick = { tile.onClick(it) }, + onLongClick = { tile.onLongClick(it) } + ) + .tileModifier(state.colors), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(iconOnly), + ) { + val icon = + remember(state.icon) { + state.icon.get().let { + if (it is QSTileImpl.ResourceIcon) { + Icon.Resource(it.resId, null) + } else { + Icon.Loaded(it.getDrawable(context), null) + } } } - } - TileContent( - label = state.label.toString(), - secondaryLabel = state.secondaryLabel.toString(), - icon = icon, - colors = state.colors, - iconOnly = iconOnly - ) + TileContent( + label = state.label.toString(), + secondaryLabel = state.secondaryLabel?.toString(), + icon = icon, + colors = state.colors, + iconOnly = iconOnly + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 259a8bfef175..b971781acd63 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -56,9 +56,8 @@ class SceneWindowRootView( } // TODO(b/298525212): remove once Compose exposes window inset bounds. - override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { - val insets = super.onApplyWindowInsets(windowInsets) - this.windowInsets.value = insets - return insets + override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets { + this.windowInsets.value = windowInsets + return windowInsets } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7983db137e76..2446473c3b31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -199,12 +199,14 @@ public class KeyguardIndicationController { protected boolean mPowerPluggedInWired; protected boolean mPowerPluggedInWireless; protected boolean mPowerPluggedInDock; + protected int mChargingSpeed; private boolean mPowerCharged; + /** Whether the battery defender is triggered. */ private boolean mBatteryDefender; + /** Whether the battery defender is triggered with the device plugged. */ private boolean mEnableBatteryDefender; private boolean mIncompatibleCharger; - protected int mChargingSpeed; private int mChargingWattage; private int mBatteryLevel; private boolean mBatteryPresent = true; @@ -1244,7 +1246,7 @@ public class KeyguardIndicationController { mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; mBatteryPresent = status.present; - mBatteryDefender = status.isBatteryDefender(); + mBatteryDefender = isBatteryDefender(status); // when the battery is overheated, device doesn't charge so only guard on pluggedIn: mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn(); mIncompatibleCharger = status.incompatibleCharger.orElse(false); @@ -1516,6 +1518,11 @@ public class KeyguardIndicationController { return mPowerPluggedIn; } + /** Return true if the device is under the battery defender mode. */ + protected boolean isBatteryDefender(BatteryStatus status) { + return status.isBatteryDefender(); + } + private boolean isCurrentUser(int userId) { return getCurrentUser() == userId; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 6a38f8df4715..d2d0aaa63003 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,6 +45,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -354,12 +357,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener, + clipSide); } else { if (onStartedRunnable != null) { onStartedRunnable.run(); @@ -378,13 +382,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mIsHeadsUpAnimation = isHeadsUpAppear; if (mDrawingAppearAnimation) { startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null, null); + duration, null, null, null, ClipSide.BOTTOM); } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); if (mAppearAnimationFraction == -1.0f) { @@ -406,9 +410,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE; targetValue = 0.0f; } + + if (NotificationHeadsUpCycling.isEnabled()) { + // TODO(b/316404716): add avalanche filtering + mCurrentAppearInterpolator = Interpolators.LINEAR; + } + mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, targetValue); - if (NotificationsImprovedHunAnimation.isEnabled()) { + if (NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled()) { mAppearAnimator.setInterpolator(mCurrentAppearInterpolator); } else { mAppearAnimator.setInterpolator(Interpolators.LINEAR); @@ -418,7 +429,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.addUpdateListener(animation -> { mAppearAnimationFraction = (float) animation.getAnimatedValue(); updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + // For cycling out, we want the HUN to be clipped from the top. + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } invalidate(); }); if (animationListener != null) { @@ -426,7 +442,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } // we need to apply the initial state already to avoid drawn frames in the wrong state updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } mAppearAnimator.addListener(new AnimatorListenerAdapter() { private boolean mRunWithoutInterruptions; @@ -508,14 +528,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView enableAppearDrawing(false); } - private void updateAppearRect() { + /** + * Update the View's Rect clipping to fit the appear animation + * @param clipSide Which side if view we want to clip from + */ + private void updateAppearRect(ClipSide clipSide) { float interpolatedFraction = - NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction + NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY; - final int actualHeight = getActualHeight(); - float bottom = actualHeight * interpolatedFraction; - + final int fullHeight = getActualHeight(); + float height = fullHeight * interpolatedFraction; if (mTargetPoint != null) { int width = getWidth(); float fraction = 1 - mAppearAnimationFraction; @@ -524,13 +548,26 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAnimationTranslationY + (mAnimationTranslationY - mTargetPoint.y) * fraction, width - (width - mTargetPoint.x) * fraction, - actualHeight - (actualHeight - mTargetPoint.y) * fraction); + fullHeight - (fullHeight - mTargetPoint.y) * fraction); } else { - setOutlineRect(0, mAppearAnimationTranslation, getWidth(), - bottom + mAppearAnimationTranslation); + if (clipSide == TOP) { + setOutlineRect( + 0, + /* top= */ fullHeight - height, + getWidth(), + /* bottom= */ fullHeight + ); + } else if (clipSide == BOTTOM) { + setOutlineRect(0, mAppearAnimationTranslation, getWidth(), + height + mAppearAnimationTranslation); + } } } + private void updateAppearRect() { + updateAppearRect(ClipSide.BOTTOM); + } + private float getInterpolatedAppearAnimationFraction() { if (mAppearAnimationFraction >= 0) { @@ -540,11 +577,36 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateAppearAnimationAlpha() { - float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction, - ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION); - float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION; - float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range; - setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha)); + updateAppearAnimationContentAlpha( + mAppearAnimationFraction, + ALPHA_APPEAR_START_FRACTION, + ALPHA_APPEAR_END_FRACTION, + Interpolators.ALPHA_IN + ); + } + + /** + * Update the alpha value of the content view during the appear animation. We suppose that the + * content alpha changes from 0 to 1 during some part of the appear animation. + * @param appearFraction the current appearFraction, should be in the range of [0, 1], where + * 1 represents fully appeared + * @param startFraction the appear fraction when the content view should be + * * fully transparent + * @param endFraction the appear fraction when the content view should be + * fully in-transparent, should be greater or equals to startFraction + * @param interpolator the interpolator to update the alpha + */ + private void updateAppearAnimationContentAlpha( + float appearFraction, + float startFraction, + float endFraction, + Interpolator interpolator + ) { + float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction, + endFraction); + float range = endFraction - startFraction; + float alpha = (contentAlphaProgress - startFraction) / range; + setContentAlpha(interpolator.getInterpolation(alpha)); } private void setContentAlpha(float contentAlpha) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 23c0a0db04d5..747cb3c8d934 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -3076,7 +3076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { Animator anim = getTranslateViewAnimator(0f, null /* listener */); if (anim != null) { @@ -3092,7 +3092,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - null, onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener, ClipSide.BOTTOM); } }); anim.start(); @@ -3100,7 +3100,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener, + clipSide); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 05e8717d0005..2af119f98f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -362,17 +362,17 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro /** * Perform a remove animation on this view. - * @param duration The duration of the remove animation. - * @param delay The delay of the animation + * + * @param duration The duration of the remove animation. + * @param delay The delay of the animation * @param translationDirection The direction value from [-1 ... 1] indicating in which the * animation should be performed. A value of -1 means that The * remove animation should be performed upwards, * such that the child appears to be going away to the top. 1 * Should mean the opposite. - * @param isHeadsUpAnimation Is this a headsUp animation. - * @param onFinishedRunnable A runnable which should be run when the animation is finished. - * @param animationListener An animation listener to add to the animation. - * + * @param isHeadsUpAnimation Is this a headsUp animation. + * @param onFinishedRunnable A runnable which should be run when the animation is finished. + * @param animationListener An animation listener to add to the animation. * @return The additional delay, in milliseconds, that this view needs to add before the * animation starts. */ @@ -380,7 +380,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener); + AnimatorListenerAdapter animationListener, ClipSide clipSide); + + public enum ClipSide { + TOP, + BOTTOM + } public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { performAddAnimation(delay, duration, isHeadsUpAppear, null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 162e8af47394..291dc132686b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -252,7 +252,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { // TODO: Use duration if (onStartedRunnable != null) { onStartedRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt index 0344b32dd6ad..d4f8ea385667 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -33,7 +33,12 @@ object NotificationHeadsUpCycling { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationContentAlphaOptimization() + get() = Flags.notificationHeadsUpCycling() + + /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ + @JvmStatic + inline val animateTallToShort + get() = false /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index e520957975f3..5f4e832f31a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -293,6 +293,8 @@ public class AmbientState implements Dumpable { } String getAvalancheShowingHunKey() { + // If we don't have a previous showing hun, we don't consider the showing hun as avalanche + if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return ""; return mAvalancheController.getShowingHunKey(); } @@ -300,6 +302,11 @@ public class AmbientState implements Dumpable { return mAvalancheController.getPreviousHunKey(); } + boolean isNullAvalancheKey(String key) { + if (key == null || key.isEmpty()) return true; + return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null"); + } + void setOverExpansion(float overExpansion) { mOverExpansion = overExpansion; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5551ab46262c..bd7bd596438a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -70,13 +70,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onStartedRunnable: Runnable?, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter?, + clipSide: ClipSide ): Long { return 0 } 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 232b4e993f06..bfc74250cefe 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 @@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; @@ -151,7 +152,6 @@ import java.util.function.Consumer; public class NotificationStackScrollLayout extends ViewGroup implements Dumpable, NotificationScrollView { - public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE); @@ -3144,6 +3144,11 @@ public class NotificationStackScrollLayout type = row.wasJustClicked() ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; + } + } if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); @@ -3164,6 +3169,11 @@ public class NotificationStackScrollLayout if (pinnedAndClosed || shouldHunAppearFromTheBottom) { // Our custom add animation type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; + } + } } else { // Normal add animation type = AnimationEvent.ANIMATION_TYPE_ADD; @@ -6135,6 +6145,22 @@ public class NotificationStackScrollLayout .animateTopInset() .animateY() .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), }; static int[] LENGTHS = new int[]{ @@ -6186,6 +6212,12 @@ public class NotificationStackScrollLayout // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, }; static final int ANIMATION_TYPE_ADD = 0; @@ -6204,6 +6236,8 @@ public class NotificationStackScrollLayout static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13; static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14; static final int ANIMATION_TYPE_EVERYTHING = 15; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17; final long eventStartTime; final ExpandableView mChangingView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index d0cebae40c5a..0fcfc4b4b2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class StackScrollAlgorithm { private float mSmallCornerRadius; private float mLargeCornerRadius; private int mHeadsUpAppearHeightBottom; + private int mHeadsUpCyclingPadding; public StackScrollAlgorithm( Context context, @@ -99,6 +101,8 @@ public class StackScrollAlgorithm { R.dimen.heads_up_status_bar_padding); mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize( R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); @@ -348,7 +352,8 @@ public class StackScrollAlgorithm { && !firstHeadsUp && (isHeadsUp || child.isHeadsUpAnimatingAway()) && newNotificationEnd > firstHeadsUpEnd - && !ambientState.isShadeExpanded()) { + && !ambientState.isShadeExpanded() + && !skipClipBottomForCycling(child, ambientState)) { // The bottom of this view is peeking out from under the previous view. // Clip the part that is peeking out. float overlapAmount = newNotificationEnd - firstHeadsUpEnd; @@ -370,6 +375,44 @@ public class StackScrollAlgorithm { } } + /** + * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for + * the hun cycling animation. + */ + private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!isCyclingOut(view, ambientState)) return false; + // skip bottom clipping if we animate the bottom line + return NotificationHeadsUpCycling.getAnimateTallToShort(); + } + + /** + * Whether the view is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!(view instanceof ExpandableNotificationRow)) return false; + return isCyclingOut((ExpandableNotificationRow) view, ambientState); + } + + /** + * Whether the row is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingOutKey = ambientState.getAvalanchePreviousHunKey(); + return row.getEntry().getKey().equals(cyclingOutKey); + } + + /** + * Whether the row is the hun that is cycling in by the notification avalanche. + */ + public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingInKey = ambientState.getAvalancheShowingHunKey(); + return row.getEntry().getKey().equals(cyclingInKey); + } + /** Updates the dimmed and hiding sensitive states of the children. */ private void updateDimmedAndHideSensitive(AmbientState ambientState, StackScrollAlgorithmState algorithmState) { @@ -799,6 +842,7 @@ public class StackScrollAlgorithm { } ExpandableNotificationRow topHeadsUpEntry = null; + int cyclingInHunHeight = -1; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); if (!(child instanceof ExpandableNotificationRow row)) { @@ -839,6 +883,13 @@ public class StackScrollAlgorithm { childState.setYTranslation( Math.max(childState.getYTranslation(), headsUpTranslation)); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); + if (NotificationHeadsUpCycling.isEnabled()) { + if (isCyclingIn(row, ambientState)) { + if (cyclingInHunHeight == -1) { + cyclingInHunHeight = childState.height; + } + } + } childState.hidden = false; ExpandableViewState topState = topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState(); @@ -860,6 +911,26 @@ public class StackScrollAlgorithm { } } if (row.isHeadsUpAnimatingAway()) { + if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) { + // If the two HUNs in the cycling animation have different heights, we need + // an extra y translation to align the animation. + int extraTranslation; + if (NotificationHeadsUpCycling.getAnimateTallToShort()) { + if (cyclingInHunHeight > 0) { + extraTranslation = cyclingInHunHeight - childState.height; + } else { + extraTranslation = 0; + } + } else { + extraTranslation = cyclingInHunHeight >= childState.height + ? cyclingInHunHeight - childState.height : 0; + } + extraTranslation += mHeadsUpCyclingPadding; + float inSpaceTranslation = Math.max(childState.getYTranslation(), + headsUpTranslation); + childState.setYTranslation(inSpaceTranslation + extraTranslation); + cyclingInHunHeight = -1; + } else if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) { if (shouldHunAppearFromBottom(ambientState, childState)) { // move to the bottom of the screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 5963d358443e..5dc544993ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; @@ -57,6 +59,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400; + public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400; public static final int ANIMATION_DURATION_FOLD_TO_AOD = AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD; public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500; @@ -68,6 +71,8 @@ public class StackStateAnimator { @VisibleForTesting int mGoToFullShadeAppearingTranslation; @VisibleForTesting float mHeadsUpAppearStartAboveScreen; + // Padding between the old and new heads up notifications for the hun cycling animation + private float mHeadsUpCyclingPadding; private final ExpandableViewState mTmpState = new ExpandableViewState(); private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; @@ -125,6 +130,8 @@ public class StackStateAnimator { R.dimen.go_to_full_shade_appearing_translation); mHeadsUpAppearStartAboveScreen = context.getResources() .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); } protected void setLogger(StackStateLogger logger) { @@ -449,7 +456,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - startAnimation, postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener(), + ExpandableView.ClipSide.BOTTOM); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { @@ -464,6 +472,27 @@ public class StackStateAnimator { .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView; row.prepareExpansionChanged(); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) { + mHeadsUpAppearChildren.add(changingView); + + mTmpState.copyFrom(changingView.getViewState()); + mTmpState.setYTranslation(changingView.getViewState().getYTranslation() + + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom)); + mTmpState.applyToView(changingView); + + // TODO(b/339519404): use a different interpolator + Runnable onAnimationEnd = null; + if (loggable) { + // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with + // normal ADD animations, which would not be logged here. + String finalKey = key; + mLogger.logHUNViewAppearing(key); + onAnimationEnd = () -> { + mLogger.appearAnimationEnded(finalKey); + }; + } + changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING, + /* isHeadsUpAppear= */ true, onAnimationEnd); } else if (NotificationsImprovedHunAnimation.isEnabled() && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) { mHeadsUpAppearChildren.add(changingView); @@ -486,6 +515,87 @@ public class StackStateAnimator { } changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR, /* isHeadsUpAppear= */ true, onAnimationEnd); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) { + mHeadsUpDisappearChildren.add(changingView); + Runnable endRunnable = null; + mTmpState.copyFrom(changingView.getViewState()); + + if (changingView.getParent() == null) { + // This notification was actually removed, so we need to add it + // transiently + mHostLayout.addTransientView(changingView, 0); + changingView.setTransientContainer(mHostLayout); + // TODO(b/316404716): remove the hard-coded height + // StackScrollAlgorithm cannot find this view because it has been removed + // from the NSSL. To correctly translate the view to the top or bottom of + // the screen (where it animated from), we need to update its translation. + mTmpState.setYTranslation( + mTmpState.getYTranslation() + 10 + ); + endRunnable = changingView::removeFromTransientContainer; + } + + boolean needsAnimation = true; + if (changingView instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; + if (row.isDismissed()) { + needsAnimation = false; + } + } + if (needsAnimation) { + // We need to add the global animation listener, since once no animations are + // running anymore, the panel will instantly hide itself. We need to wait until + // the animation is fully finished for this though. + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; + if (loggable) { + String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + + }; + } else { + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; + } + long removeAnimationDelay = changingView.performRemoveAnimation( + ANIMATION_DURATION_HEADS_UP_CYCLING, + /* delay= */ 0, + // It's a shame that translationDirection isn't where we do the y + // translation, the actual translation is in StackScrollAlgorithm. + /* translationDirection= */ 0.0f, + /* isHeadsUpAnimation= */ true, + startAnimation, postAnimation, + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP); + mAnimationProperties.delay += removeAnimationDelay; + mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING; + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.LINEAR); + mAnimationProperties.getAnimationFilter().animateY = true; + mTmpState.animateTo(changingView, mAnimationProperties); + } else if (endRunnable != null) { + endRunnable.run(); + } + needsCustomAnimation |= needsAnimation; } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) { NotificationsImprovedHunAnimation.assertInLegacyMode(); // This item is added, initialize its properties. @@ -565,21 +675,21 @@ public class StackStateAnimator { } }; } else { + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; postAnimation = () -> { changingView.setInRemovalAnimation(false); if (tmpEndRunnable != null) { tmpEndRunnable.run(); } }; - startAnimation = () -> { - changingView.setInRemovalAnimation(true); - }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, startAnimation, postAnimation, - getGlobalAnimationFinishedListener()); + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM); mAnimationProperties.delay += removeAnimationDelay; if (NotificationsImprovedHunAnimation.isEnabled()) { mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; @@ -607,6 +717,38 @@ public class StackStateAnimator { return -mStackTopMargin - mHeadsUpAppearStartAboveScreen; } + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @return The start y translation of the HUN cycling in animation + */ + private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) { + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding; + } + // start from the top of the screen + return -mHeadsUpCyclingPadding; + } + + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @param oldHunHeight Height of the old HUN + * @param newHunHeight Height of the new HUN + * @return The y translation target value of the HUN cycling out animation + */ + private float getHeadsUpCyclingOutYTranslation( + boolean headsUpFromBottom, + int oldHunHeight, + int newHunHeight + ) { + final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight; + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom - translationDistance; + } + return translationDistance; + } + public void animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index 25e5470e2781..3164f8e11593 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -23,11 +25,17 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -39,6 +47,7 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; @@ -47,6 +56,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,9 +68,12 @@ import org.mockito.MockitoAnnotations; */ @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class IMagnificationConnectionTest extends SysuiTestCase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock private AccessibilityManager mAccessibilityManager; @@ -90,6 +103,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { private IMagnificationConnection mIMagnificationConnection; private Magnification mMagnification; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private TestableLooper mTestableLooper; @Before public void setUp() throws Exception { @@ -100,8 +114,10 @@ public class IMagnificationConnectionTest extends SysuiTestCase { return null; }).when(mAccessibilityManager).setMagnificationConnection( any(IMagnificationConnection.class)); + mTestableLooper = TestableLooper.get(this); + assertNotNull(mTestableLooper); mMagnification = new Magnification(getContext(), - getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue, + mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue, mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger); mMagnification.mWindowMagnificationControllerSupplier = @@ -122,7 +138,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void enableWindowMagnification_passThrough() throws RemoteException { mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, Float.NaN, 0f, 0f, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f), eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback)); @@ -131,7 +147,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException { mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true); - waitForIdleSync(); + processAllPendingMessages(); verify(mFullscreenMagnificationController) .onFullscreenMagnificationActivationChanged(eq(true)); @@ -141,7 +157,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException { mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).deleteWindowMagnification( mAnimationCallback); @@ -150,7 +166,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void setScaleForWindowMagnification() throws RemoteException { mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).setScale(3.0f); } @@ -158,7 +174,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void moveWindowMagnifier() throws RemoteException { mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f); } @@ -167,37 +183,102 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void moveWindowMagnifierToPosition() throws RemoteException { mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY, 100f, 200f, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).moveWindowMagnifierToPosition( eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class)); } @Test - public void showMagnificationButton() throws RemoteException { + @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException { // magnification settings panel should not be showing assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - waitForIdleSync(); + processAllPendingMessages(); + + verify(mModeSwitchesController).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException { + // magnification settings panel should not be showing + assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // showMagnificationButton request to Magnification. + processAllPendingMessages(); + + // The delayed message would be processed after DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS. + // So call this processAllPendingMessages with a timeout to verify the showButton + // will be called. + int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100; + processAllPendingMessages(timeout); verify(mModeSwitchesController).showButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @Test + public void showMagnificationButton_settingsPanelShowing_doNotShowButton() + throws RemoteException { + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(true); + + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // showMagnificationButton request to Magnification. + processAllPendingMessages(); + + // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so + // process all message after a timeout here to verify the showButton will not be called. + int timeout = Flags.delayShowMagnificationButton() + ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100 + : 0; + processAllPendingMessages(timeout); + verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test public void removeMagnificationButton() throws RemoteException { mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); - waitForIdleSync(); + processAllPendingMessages(); verify(mModeSwitchesController).removeButton(TEST_DISPLAY); } @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() + throws RemoteException { + // magnification settings panel should not be showing + assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // requests to Magnification. + processAllPendingMessages(); + + // Call this processAllPendingMessages with a timeout to ensure the delayed show button + // message should be removed and thus the showButton will not be called after timeout. + int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100; + processAllPendingMessages(/* timeForwardMs= */ timeout); + verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test public void removeMagnificationSettingsPanel() throws RemoteException { mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY); - waitForIdleSync(); + processAllPendingMessages(); verify(mMagnificationSettingsController).closeMagnificationSettings(); } @@ -208,7 +289,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { final float testScale = 3.0f; mIMagnificationConnection.onUserMagnificationScaleChanged( testUserId, TEST_DISPLAY, testScale); - waitForIdleSync(); + processAllPendingMessages(); assertTrue(mMagnification.mUsersScales.contains(testUserId)); assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY), @@ -216,6 +297,17 @@ public class IMagnificationConnectionTest extends SysuiTestCase { verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale)); } + private void processAllPendingMessages() { + processAllPendingMessages(/* timeForwardMs=*/ 0); + } + + private void processAllPendingMessages(int timeForwardMs) { + if (timeForwardMs > 0) { + mTestableLooper.moveTimeForward(timeForwardMs); + } + mTestableLooper.processAllMessages(); + } + private class FakeWindowMagnificationControllerSupplier extends DisplayIdIndexSupplier<WindowMagnificationController> { @@ -229,7 +321,6 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } } - private class FakeFullscreenMagnificationControllerSupplier extends DisplayIdIndexSupplier<FullscreenMagnificationController> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index 2172bc5ee8e1..8695c01e89d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -5,12 +5,12 @@ import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.promptInfo import com.android.systemui.biometrics.shared.model.BiometricUserInfo +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -110,7 +110,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.description = description it.subtitle = subtitle }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -135,7 +135,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -163,7 +163,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -171,13 +171,13 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(showTitleOnly).isFalse() } - @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN) + @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin) - @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD) + @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password) - @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN) + @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern) - private fun useCredentialForPrompt(kind: Int) = + private fun useCredentialForPrompt(kind: PromptKind) = testScope.runTest { val isStealth = false credentialInteractor.stealthMode = isStealth @@ -211,11 +211,10 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(prompt) .isInstanceOf( when (kind) { - Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java - Utils.CREDENTIAL_PASSWORD -> + PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java + PromptKind.Password -> BiometricPromptRequest.Credential.Password::class.java - Utils.CREDENTIAL_PATTERN -> - BiometricPromptRequest.Credential.Pattern::class.java + PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java else -> throw Exception("wrong kind") } ) @@ -341,6 +340,28 @@ class PromptCredentialInteractorTest : SysuiTestCase() { job.cancel() } + + /** Update the current request to use credential-based authentication instead of biometrics. */ + private fun PromptCredentialInteractor.useCredentialsForAuthentication( + promptInfo: PromptInfo, + kind: PromptKind, + userId: Int, + challenge: Long, + opPackageName: String, + ) { + biometricPromptRepository.setPrompt( + promptInfo, + userId, + challenge, + kind, + opPackageName, + ) + } + + /** Unset the current authentication request. */ + private fun PromptCredentialInteractor.resetPrompt() { + biometricPromptRepository.unsetPrompt() + } } private fun pinRequest(): BiometricPromptRequest.Credential.Pin = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 2817780cbf03..c308507fdd90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -22,7 +22,6 @@ import android.hardware.biometrics.PromptInfo import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.faceSensorPropertiesInternal @@ -143,21 +142,20 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { } @Test - fun usePinCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) } + fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) } @Test fun usePatternCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) } + testScope.runTest { useCredentialAndReset(PromptKind.Pattern) } @Test fun usePasswordCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) } + testScope.runTest { useCredentialAndReset(PromptKind.Password) } - private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) { + private fun TestScope.useCredentialAndReset(kind: PromptKind) { setUserCredentialType( - isPin = kind == Utils.CREDENTIAL_PIN, - isPassword = kind == Utils.CREDENTIAL_PASSWORD, + isPin = kind == PromptKind.Pin, + isPassword = kind == PromptKind.Password, ) val info = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 687e91ad0b81..c3a806ba8d43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState @@ -1424,6 +1425,39 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE) + fun dreamingToOccludedToDreaming() = + testScope.runTest { + // GIVEN a device on lockscreen + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + // Given a device that is dreaming + keyguardRepository.setDreaming(true) + + // GIVEN a prior transition has run to OCCLUDED + runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // THEN a transition to GLANCEABLE_HUB should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = FromOccludedTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNotNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test fun dreamingToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to DREAMING diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index a77169e74de5..b1a8dd1d3fdc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -18,29 +18,20 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.flags.DisableSceneContainer -import com.android.systemui.flags.EnableSceneContainer 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.scene.data.repository.sceneContainerRepository -import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -66,22 +57,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { .thenReturn(surfaceBehindIsAnimatingFlow) } - private val underTest = lazy { kosmos.windowManagerLockscreenVisibilityInteractor } + private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor private val testScope = kosmos.testScope private val transitionRepository = kosmos.fakeKeyguardTransitionRepository - @Before - fun setUp() { - // lazy value needs to be called here otherwise flow collection misbehaves - underTest.value - kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) - } - @Test - @DisableSceneContainer fun surfaceBehindVisibility_switchesToCorrectFlow() = testScope.runTest { - val values by collectValues(underTest.value.surfaceBehindVisibility) + val values by collectValues(underTest.surfaceBehindVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -187,10 +170,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test - @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { - val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -248,10 +230,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test - @DisableSceneContainer fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() = testScope.runTest { - val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -338,10 +319,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test - @DisableSceneContainer fun lockscreenVisibility_visibleWhenGone() = testScope.runTest { - val values by collectValues(underTest.value.lockscreenVisibility) + val values by collectValues(underTest.lockscreenVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -405,10 +385,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test - @DisableSceneContainer fun testLockscreenVisibility_usesFromState_ifCanceled() = testScope.runTest { - val values by collectValues(underTest.value.lockscreenVisibility) + val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -507,10 +486,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). */ @Test - @DisableSceneContainer fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = testScope.runTest { - val values by collectValues(underTest.value.lockscreenVisibility) + val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -609,11 +587,11 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) } + /** */ @Test - @DisableSceneContainer fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = testScope.runTest { - val values by collectValues(underTest.value.lockscreenVisibility) + val values by collectValues(underTest.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -724,109 +702,4 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } - - @Test - @EnableSceneContainer - fun sceneContainer_lockscreenVisibility_visibleWhenNotGone() = - testScope.runTest { - val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) - - sceneTransitions.value = lsToGone - assertThat(lockscreenVisibility).isTrue() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = goneToLs - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) - assertThat(lockscreenVisibility).isTrue() - } - - @Test - @EnableSceneContainer - fun sceneContainer_lockscreenVisibility_notVisibleWhenReturningToGone() = - testScope.runTest { - val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) - - sceneTransitions.value = goneToLs - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = lsToGone - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = goneToLs - assertThat(lockscreenVisibility).isFalse() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) - assertThat(lockscreenVisibility).isTrue() - } - - @Test - @EnableSceneContainer - fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() = - testScope.runTest { - val usingKeyguardGoingAwayAnimation by - collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) - - sceneTransitions.value = lsToGone - assertThat(usingKeyguardGoingAwayAnimation).isTrue() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) - assertThat(usingKeyguardGoingAwayAnimation).isFalse() - } - - @Test - @EnableSceneContainer - fun sceneContainer_usingGoingAwayAnimation_surfaceBehindIsAnimating() = - testScope.runTest { - val usingKeyguardGoingAwayAnimation by - collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) - - sceneTransitions.value = lsToGone - surfaceBehindIsAnimatingFlow.emit(true) - assertThat(usingKeyguardGoingAwayAnimation).isTrue() - - sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) - assertThat(usingKeyguardGoingAwayAnimation).isTrue() - - sceneTransitions.value = goneToLs - assertThat(usingKeyguardGoingAwayAnimation).isTrue() - - surfaceBehindIsAnimatingFlow.emit(false) - assertThat(usingKeyguardGoingAwayAnimation).isFalse() - } - - companion object { - private val progress = MutableStateFlow(0f) - - private val sceneTransitions = - MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(Scenes.Lockscreen) - ) - - private val lsToGone = - ObservableTransitionState.Transition( - Scenes.Lockscreen, - Scenes.Gone, - flowOf(Scenes.Lockscreen), - progress, - false, - flowOf(false) - ) - - private val goneToLs = - ObservableTransitionState.Transition( - Scenes.Gone, - Scenes.Lockscreen, - flowOf(Scenes.Lockscreen), - progress, - false, - flowOf(false) - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index 4f0f91a7ee56..926c35f32967 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -134,7 +134,8 @@ class StackStateAnimatorTest : SysuiTestCase() { /* isHeadsUpAnimation= */ eq(true), /* onStartedRunnable= */ any(), /* onFinishedRunnable= */ runnableCaptor.capture(), - /* animationListener= */ any() + /* animationListener= */ any(), + /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM), ) animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index aac36405e9b6..56e5e293c799 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -138,6 +138,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt index b38acc8a46dc..29167d64d1f1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor val Kosmos.windowManagerLockscreenVisibilityInteractor by @@ -30,6 +29,5 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, - sceneInteractor = sceneInteractor, ) } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 763879e5379a..6fc05b72da9b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -1998,6 +1998,19 @@ public final class AutofillManagerService } @Override + public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.setViewAutofilled(sessionId, getCallingUid(), id); + } else if (sVerbose) { + Slog.v(TAG, "setAutofillFailure(): no service for " + userId); + } + } + } + + @Override public void finishSession(int sessionId, int userId, @AutofillCommitReason int commitReason) { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 92acce24e64e..588266fba47a 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -466,6 +466,7 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } final Session session = mSessions.get(sessionId); @@ -477,8 +478,23 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") + void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")"); + return; + } + session.setViewAutofilled(id); + } + + @GuardedBy("mLock") void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 9c84b123a435..f289115159b8 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -65,6 +65,7 @@ import android.content.pm.PackageManager; import android.provider.Settings; import android.service.autofill.Dataset; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -548,9 +549,10 @@ public final class PresentationStatsEventLogger { /** * Set views_fillable_total_count as long as mEventInternal presents. */ - public void maybeSetViewFillableCounts(int totalFillableCount) { + public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) { mEventInternal.ifPresent(event -> { - event.mViewFillableTotalCount = totalFillableCount; + event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds); + event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size(); }); } @@ -564,6 +566,41 @@ public final class PresentationStatsEventLogger { }); } + /** Sets focused_autofill_id using view id */ + public void maybeSetFocusedId(AutofillId id) { + maybeSetFocusedId(id.getViewId()); + } + + /** Sets focused_autofill_id as long as mEventInternal is present */ + public void maybeSetFocusedId(int id) { + mEventInternal.ifPresent(event -> { + event.mFocusedId = id; + }); + } + /** + * Set views_filled_failure_count using failure count as long as mEventInternal + * presents. + */ + public void maybeAddSuccessId(AutofillId autofillId) { + mEventInternal.ifPresent(event -> { + ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill; + if (autofillIds == null) { + Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId + + " successfully filled"); + event.mViewFilledButUnexpectedCount++; + } else if (autofillIds.contains(autofillId)) { + if (sVerbose) { + Slog.v(TAG, "Logging autofill for id:" + autofillId); + event.mViewFillSuccessCount++; + } + } else { + Slog.w(TAG, "Successfully filled autofillId:" + autofillId + + " not found in list of attempted autofill ids: " + autofillIds); + event.mViewFilledButUnexpectedCount++; + } + }); + } + public void logAndEndEvent() { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " @@ -608,7 +645,10 @@ public final class PresentationStatsEventLogger { + " mIsCredentialRequest=" + event.mIsCredentialRequest + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential + " mViewFillableTotalCount=" + event.mViewFillableTotalCount - + " mViewFillFailureCount=" + event.mViewFillFailureCount); + + " mViewFillFailureCount=" + event.mViewFillFailureCount + + " mFocusedId=" + event.mFocusedId + + " mViewFillSuccessCount=" + event.mViewFillSuccessCount + + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -651,7 +691,10 @@ public final class PresentationStatsEventLogger { event.mIsCredentialRequest, event.mWebviewRequestedCredential, event.mViewFillableTotalCount, - event.mViewFillFailureCount); + event.mViewFillFailureCount, + event.mFocusedId, + event.mViewFillSuccessCount, + event.mViewFilledButUnexpectedCount); mEventInternal = Optional.empty(); } @@ -689,7 +732,14 @@ public final class PresentationStatsEventLogger { boolean mWebviewRequestedCredential = false; int mViewFillableTotalCount = -1; int mViewFillFailureCount = -1; + int mFocusedId = -1; + + // Default value for success count is set to 0 explicitly. Setting it to -1 for + // uninitialized doesn't help much, as this would be non-zero only if callback is received. + int mViewFillSuccessCount = 0; + int mViewFilledButUnexpectedCount = 0; + ArraySet<AutofillId> mAutofillIdsAttemptedAutofill; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8b13c4b762d5..07b16c53ffe8 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4669,6 +4669,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFieldClassificationIdSnapshot); mPresentationStatsEventLogger.maybeSetAvailableCount( response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); } @GuardedBy("mLock") @@ -5381,7 +5382,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size()); - mPresentationStatsEventLogger.logAndEndEvent(); + } + + /** + * Sets the state of views that failed to autofill. + */ + @GuardedBy("mLock") + void setViewAutofilled(@NonNull AutofillId id) { + if (sVerbose) { + Slog.v(TAG, "View autofilled: " + id); + } + if (id.getSessionId() == AutofillId.NO_SESSION) { + id.setSessionId(this.id); + } + mPresentationStatsEventLogger.maybeAddSuccessId(id); } @GuardedBy("mLock") @@ -6589,7 +6603,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) { Slog.v(TAG, "Total views to be autofilled: " + ids.size()); } - mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size()); + mPresentationStatsEventLogger.maybeSetViewFillablesAndCount(ids); if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); mClient.autofill(id, ids, values, hideHighlight); if (dataset.getId() != null) { diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java index 70443f9153d1..38a412fa063d 100644 --- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java +++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java @@ -118,7 +118,7 @@ final class RemoteInlineSuggestionViewConnector { final InputMethodManagerInternal inputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken, - displayId)) { + displayId, mUserId)) { Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME"); mOnErrorCallback.run(); } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index d7c65c748d8f..70af49c88433 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -224,7 +224,7 @@ class InputController { token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer()); String phys = inputDeviceDescriptor.getPhys(); - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); // Type associations are added in the case of navigation touchpads. Those should be removed // once the input device gets closed. if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) { @@ -287,7 +287,7 @@ class InputController { private void setUniqueIdAssociation(int displayId, String phys) { final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId; - InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId); + InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId); } boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { @@ -789,7 +789,7 @@ class InputController { throw e; } } catch (DeviceCreationException e) { - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); throw e; } diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 748253fc9194..1a8c3b086341 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -35,7 +35,6 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.BundleMerger; import android.os.Debug; import android.os.DropBoxManager; @@ -176,6 +175,16 @@ public final class DropBoxManagerService extends SystemService { } }; + private static final BundleMerger sDropboxEntryAddedExtrasMerger; + static { + sDropboxEntryAddedExtrasMerger = new BundleMerger(); + sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST); + sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME, + BundleMerger.STRATEGY_COMPARABLE_MAX); + sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, + BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); + } + private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() { @Override public void addData(String tag, byte[] data, int flags) { @@ -284,7 +293,7 @@ public final class DropBoxManagerService extends SystemService { public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_BROADCAST: - prepareAndSendBroadcast((Intent) msg.obj, null); + prepareAndSendBroadcast((Intent) msg.obj, false); break; case MSG_SEND_DEFERRED_BROADCAST: Intent deferredIntent; @@ -292,31 +301,42 @@ public final class DropBoxManagerService extends SystemService { deferredIntent = mDeferredMap.remove((String) msg.obj); } if (deferredIntent != null) { - prepareAndSendBroadcast(deferredIntent, - createBroadcastOptions(deferredIntent)); + prepareAndSendBroadcast(deferredIntent, true); } break; } } - private void prepareAndSendBroadcast(Intent intent, Bundle options) { + private void prepareAndSendBroadcast(Intent intent, boolean deferrable) { if (!DropBoxManagerService.this.mBooted) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } + final BroadcastOptions options = BroadcastOptions.makeBasic(); if (Flags.enableReadDropboxPermission()) { - BroadcastOptions unbundledOptions = (options == null) - ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options); - - unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG) + + "-READ_DROPBOX_DATA"; + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle()); + Manifest.permission.READ_DROPBOX_DATA, options.toBundle()); - unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG) + + "-READ_LOGS"; + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - Manifest.permission.READ_LOGS, unbundledOptions.toBundle()); + Manifest.permission.READ_LOGS, options.toBundle()); } else { + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG); + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_LOGS, options); + android.Manifest.permission.READ_LOGS, options.toBundle()); } } @@ -328,21 +348,12 @@ public final class DropBoxManagerService extends SystemService { return dropboxIntent; } - private Bundle createBroadcastOptions(Intent intent) { - final BundleMerger extrasMerger = new BundleMerger(); - extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST); - extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME, - BundleMerger.STRATEGY_COMPARABLE_MAX); - extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, - BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); - - return BroadcastOptions.makeBasic() - .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) + private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) { + options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED, - intent.getStringExtra(DropBoxManager.EXTRA_TAG)) - .setDeliveryGroupExtrasMerger(extrasMerger) - .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) - .toBundle(); + matchingKey) + .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); } /** diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index bef5c6125551..94bf813d3696 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -157,6 +157,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException; import android.app.Service; import android.app.ServiceStartArgs; import android.app.StartForegroundCalledOnStoppedServiceException; @@ -784,7 +785,7 @@ public final class ActiveServices { ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, "SERVICE_FOREGROUND_TIMEOUT"); this.mFGSAnrTimer = new ServiceAnrTimer(service, - ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG, + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, "FGS_TIMEOUT"); } @@ -2456,12 +2457,14 @@ public final class ActiveServices { + " foreground service type " + ServiceInfo.foregroundServiceTypeToLabel( foregroundServiceType); - if (!android.app.Flags.gateFgsTimeoutAnrBehavior()) { + // Only throw an exception if the new ANR behavior + // ("do nothing") is not gated or the new crashing logic gate + // is enabled; otherwise, reset the limit temporarily. + if (!android.app.Flags.gateFgsTimeoutAnrBehavior() + || android.app.Flags.enableFgsTimeoutCrashBehavior()) { throw new ForegroundServiceStartNotAllowedException( exceptionMsg); } else { - // Only throw an exception above while the new ANR behavior - // is not gated, otherwise, reset the limit temporarily. Slog.wtf(TAG, exceptionMsg); fgsTypeInfo.reset(); } @@ -3936,12 +3939,12 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e); } - // ANR the service after giving the service some time to clean up. - mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration); + // Crash the service after giving the service some time to clean up. + mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration); } } - void onFgsAnrTimeout(ServiceRecord sr) { + void onFgsCrashTimeout(ServiceRecord sr) { final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType); if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { return; // no timed out FGS type was found (either it was stopped or it switched types) @@ -3956,20 +3959,36 @@ public final class ActiveServices { return; } - final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); - tr.mLatencyTracker.waitingOnAMSLockStarted(); - synchronized (mAm) { - tr.mLatencyTracker.waitingOnAMSLockEnded(); - - Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); - traceInstant("FGS ANR: ", sr); - if (sr.app != null) { - mAm.appNotResponding(sr.app, tr); + if (android.app.Flags.enableFgsTimeoutCrashBehavior()) { + // Crash the app + synchronized (mAm) { + Slog.e(TAG_SERVICE, "FGS Crashed: " + sr); + traceInstant("FGS Crash: ", sr); + if (sr.app != null) { + mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(), + sr.app.info.packageName, sr.app.userId, reason, false /*force*/, + ForegroundServiceDidNotStopInTimeException.TYPE_ID, + ForegroundServiceDidNotStopInTimeException + .createExtrasForService(sr.getComponentName())); + } } + } else { + // ANR the app if the new crash behavior is not enabled + final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); + tr.mLatencyTracker.waitingOnAMSLockStarted(); + synchronized (mAm) { + tr.mLatencyTracker.waitingOnAMSLockEnded(); - // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR - // dialog really doesn't remember the "cause" (especially if there have been multiple - // ANRs), so it's not doable. + Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); + traceInstant("FGS ANR: ", sr); + if (sr.app != null) { + mAm.appNotResponding(sr.app, tr); + } + + // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR + // dialog really doesn't remember the "cause" (especially if there have been + // multiple ANRs), so it's not doable. + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 9e06b7535cdf..26aa0535d43e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1115,17 +1115,17 @@ final class ActivityManagerConstants extends ContentObserver { /** * If a service of a timeout-enforced type doesn't finish within this duration after its - * timeout, then we'll declare an ANR. + * timeout, then we'll crash the app. * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then - * the app will be ANR'ed 1 hour and 10 seconds after it started. + * the app will crash 1 hour and 10 seconds after it started. */ - private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration"; + private static final String KEY_FGS_CRASH_EXTRA_WAIT_DURATION = "fgs_crash_extra_wait_duration"; - /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ - static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000; + /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */ + static final long DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION = 10_000; - /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ - public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION; + /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */ + public volatile long mFgsCrashExtraWaitDuration = DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION; /** @see #KEY_USE_TIERED_CACHED_ADJ */ public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ; @@ -1315,8 +1315,8 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION: updateShortFgsAnrExtraWaitDuration(); break; - case KEY_FGS_ANR_EXTRA_WAIT_DURATION: - updateFgsAnrExtraWaitDuration(); + case KEY_FGS_CRASH_EXTRA_WAIT_DURATION: + updateFgsCrashExtraWaitDuration(); break; case KEY_PROACTIVE_KILLS_ENABLED: updateProactiveKillsEnabled(); @@ -2199,11 +2199,11 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION); } - private void updateFgsAnrExtraWaitDuration() { - mFgsAnrExtraWaitDuration = DeviceConfig.getLong( + private void updateFgsCrashExtraWaitDuration() { + mFgsCrashExtraWaitDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - KEY_FGS_ANR_EXTRA_WAIT_DURATION, - DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION); + KEY_FGS_CRASH_EXTRA_WAIT_DURATION, + DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION); } private void updateEnableWaitForFinishAttachApplication() { @@ -2456,8 +2456,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration); pw.print(" "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION); pw.print("="); pw.println(mDataSyncFgsTimeoutDuration); - pw.print(" "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION); - pw.print("="); pw.println(mFgsAnrExtraWaitDuration); + pw.print(" "); pw.print(KEY_FGS_CRASH_EXTRA_WAIT_DURATION); + pw.print("="); pw.println(mFgsCrashExtraWaitDuration); pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ); pw.print("="); pw.println(USE_TIERED_CACHED_ADJ); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 00c2df67c8f0..46ed1fd79874 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1670,7 +1670,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; static final int SERVICE_FGS_TIMEOUT_MSG = 84; - static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85; + static final int SERVICE_FGS_CRASH_TIMEOUT_MSG = 85; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2041,8 +2041,8 @@ public class ActivityManagerService extends IActivityManager.Stub case SERVICE_FGS_TIMEOUT_MSG: { mServices.onFgsTimeout((ServiceRecord) msg.obj); } break; - case SERVICE_FGS_ANR_TIMEOUT_MSG: { - mServices.onFgsAnrTimeout((ServiceRecord) msg.obj); + case SERVICE_FGS_CRASH_TIMEOUT_MSG: { + mServices.onFgsCrashTimeout((ServiceRecord) msg.obj); } break; } } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index cc6ae5ca03e3..8647750d510f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3277,7 +3277,13 @@ public class OomAdjuster { } final int curSchedGroup = state.getCurrentSchedulingGroup(); - if (state.getSetSchedGroup() != curSchedGroup) { + if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 + && ActivityManager.isProcStateBackground(state.getCurProcState()) + && !state.hasStartedServices()) { + app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, + ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); + success = false; + } else if (state.getSetSchedGroup() != curSchedGroup) { int oldSchedGroup = state.getSetSchedGroup(); state.setSetSchedGroup(curSchedGroup); if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { @@ -3285,75 +3291,67 @@ public class OomAdjuster { + " to " + curSchedGroup + ": " + state.getAdjType(); reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); } - if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 - && ActivityManager.isProcStateBackground(state.getSetProcState()) - && !state.hasStartedServices()) { - app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); - success = false; - } else { - int processGroup; - switch (curSchedGroup) { - case SCHED_GROUP_BACKGROUND: - processGroup = THREAD_GROUP_BACKGROUND; - break; - case SCHED_GROUP_TOP_APP: - case SCHED_GROUP_TOP_APP_BOUND: - processGroup = THREAD_GROUP_TOP_APP; - break; - case SCHED_GROUP_RESTRICTED: - processGroup = THREAD_GROUP_RESTRICTED; - break; - default: - processGroup = THREAD_GROUP_DEFAULT; - break; - } - mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( - 0 /* unused */, app.getPid(), processGroup, app.processName)); - try { - final int renderThreadTid = app.getRenderThreadTid(); - if (curSchedGroup == SCHED_GROUP_TOP_APP) { - // do nothing if we already switched to RT - if (oldSchedGroup != SCHED_GROUP_TOP_APP) { - app.getWindowProcessController().onTopProcChanged(); - if (app.useFifoUiScheduling()) { - // Switch UI pipeline for app to SCHED_FIFO - state.setSavedPriority(Process.getThreadPriority(app.getPid())); - ActivityManagerService.setFifoPriority(app, true /* enable */); - } else { - // Boost priority for top app UI and render threads - setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); - if (renderThreadTid != 0) { - try { - setThreadPriority(renderThreadTid, - THREAD_PRIORITY_TOP_APP_BOOST); - } catch (IllegalArgumentException e) { - // thread died, ignore - } - } - } - } - } else if (oldSchedGroup == SCHED_GROUP_TOP_APP - && curSchedGroup != SCHED_GROUP_TOP_APP) { + int processGroup; + switch (curSchedGroup) { + case SCHED_GROUP_BACKGROUND: + processGroup = THREAD_GROUP_BACKGROUND; + break; + case SCHED_GROUP_TOP_APP: + case SCHED_GROUP_TOP_APP_BOUND: + processGroup = THREAD_GROUP_TOP_APP; + break; + case SCHED_GROUP_RESTRICTED: + processGroup = THREAD_GROUP_RESTRICTED; + break; + default: + processGroup = THREAD_GROUP_DEFAULT; + break; + } + mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( + 0 /* unused */, app.getPid(), processGroup, app.processName)); + try { + final int renderThreadTid = app.getRenderThreadTid(); + if (curSchedGroup == SCHED_GROUP_TOP_APP) { + // do nothing if we already switched to RT + if (oldSchedGroup != SCHED_GROUP_TOP_APP) { app.getWindowProcessController().onTopProcChanged(); if (app.useFifoUiScheduling()) { - // Reset UI pipeline to SCHED_OTHER - ActivityManagerService.setFifoPriority(app, false /* enable */); - setThreadPriority(app.getPid(), state.getSavedPriority()); + // Switch UI pipeline for app to SCHED_FIFO + state.setSavedPriority(Process.getThreadPriority(app.getPid())); + ActivityManagerService.setFifoPriority(app, true /* enable */); } else { - // Reset priority for top app UI and render threads - setThreadPriority(app.getPid(), 0); - } - - if (renderThreadTid != 0) { - setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); + // Boost priority for top app UI and render threads + setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + if (renderThreadTid != 0) { + try { + setThreadPriority(renderThreadTid, + THREAD_PRIORITY_TOP_APP_BOOST); + } catch (IllegalArgumentException e) { + // thread died, ignore + } + } } } - } catch (Exception e) { - if (DEBUG_ALL) { - Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } else if (oldSchedGroup == SCHED_GROUP_TOP_APP + && curSchedGroup != SCHED_GROUP_TOP_APP) { + app.getWindowProcessController().onTopProcChanged(); + if (app.useFifoUiScheduling()) { + // Reset UI pipeline to SCHED_OTHER + ActivityManagerService.setFifoPriority(app, false /* enable */); + setThreadPriority(app.getPid(), state.getSavedPriority()); + } else { + // Reset priority for top app UI and render threads + setThreadPriority(app.getPid(), 0); + } + + if (renderThreadTid != 0) { + setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); } } + } catch (Exception e) { + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } } } if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 85eb044ca967..1db3483e3800 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2912,10 +2912,12 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); @@ -2952,7 +2954,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid, resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted, + Process.INVALID_UID, null, null, + Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage); if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, @@ -2970,9 +2973,9 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED; return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName, - proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } @Override @@ -3023,14 +3026,14 @@ public class AppOpsService extends IAppOpsService.Stub { } return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, Process.INVALID_UID, null, null, - AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, - String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean shouldCollectAsyncNotedOp, @Nullable String message, + String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId, + @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { PackageVerificationResult pvr; try { @@ -3161,8 +3164,9 @@ public class AppOpsService extends IAppOpsService.Stub { } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED); + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - uidState.getState(), flags); + getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, @@ -3528,9 +3532,9 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, - virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - attributionFlags, attributionChainId); + virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, + OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId); } /** @deprecated Use {@link #startProxyOperationWithState} instead. */ @@ -3568,18 +3572,32 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) { - Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId " - + proxyVirtualDeviceId + " is invalid"); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, - proxiedPackageName); + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId " + + proxyVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); + } + if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) { + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId " + + proxiedVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); } if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid)) || !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) { @@ -3621,7 +3639,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Test if the proxied operation will succeed before starting the proxy operation final SyncNotedAppOp testProxiedOp = startOperationDryRun(code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, - proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, + proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, startIfModeDefault); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { @@ -3633,7 +3651,7 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, + Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, shouldCollectMessage, proxyAttributionFlags, attributionChainId); if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { @@ -3642,9 +3660,10 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName, - proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags, + attributionChainId); } private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { @@ -3654,9 +3673,10 @@ public class AppOpsService extends IAppOpsService.Stub { private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag, - @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, - @AttributionFlags int attributionFlags, int attributionChainId) { + int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3751,13 +3771,13 @@ public class AppOpsService extends IAppOpsService.Stub { + " flags: " + AppOpsManager.flagsToString(flags)); try { if (isRestricted) { - attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); } else { - attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); startType = START_TYPE_STARTED; } } catch (RemoteException e) { @@ -4946,7 +4966,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (accessTime > 0) { attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, - proxyAttributionTag, uidState, opFlags); + proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags); } if (rejectTime > 0) { attributedOp.rejected(rejectTime, uidState, opFlags); diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 2285826c0b58..2760ccf72f98 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -24,7 +24,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.companion.virtual.VirtualDeviceManager; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -95,16 +94,17 @@ final class AttributedOp { * * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ public void accessed(int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags) { + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { long accessTime = System.currentTimeMillis(); - accessed(accessTime, -1, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags); + accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, + uidState, flags); mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags, accessTime, @@ -118,14 +118,16 @@ final class AttributedOp { * @param duration The duration of the event * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService public void accessed(long noteTime, long duration, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { long key = makeKey(uidState, flags); if (mAccessEvents == null) { @@ -135,7 +137,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); @@ -189,35 +191,36 @@ final class AttributedOp { * Update state when start was called * * @param clientId Id of the startOp caller + * @param virtualDeviceId The virtual device id of the startOp caller * @param proxyUid The UID of the proxy app * @param proxyPackageName The package name of the proxy app * @param proxyAttributionTag The attribution tag of the proxy app + * @param proxyDeviceId The device id of the proxy app * @param uidState UID state of the app startOp is called for * @param flags The proxy flags * @param attributionFlags The attribution flags associated with this operation. - * @param attributionChainId The if of the attribution chain this operations is a part of. + * @param attributionChainId The if of the attribution chain this operations is a part of */ - public void started(@NonNull IBinder clientId, int proxyUid, + public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, proxyVirtualDeviceId, uidState, flags, - /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags, - attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + true); } @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService - private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, + private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange, - boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags, - int attributionChainId) throws RemoteException { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted) + throws RemoteException { if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags, + parent.packageName, tag, virtualDeviceId, true, attributionFlags, attributionChainId); } @@ -233,9 +236,9 @@ final class AttributedOp { InProgressStartOpEvent event = events.get(clientId); if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId, + SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); } else { @@ -366,15 +369,14 @@ final class AttributedOp { /** * Create an event that will be started, if the op is unpaused. */ - public void createPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, - @AppOpsManager.AttributionFlags int attributionFlags, + public void createPaused(@NonNull IBinder clientId, int virtualDeviceId, + int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - proxyVirtualDeviceId, uidState, flags, false, false, - attributionFlags, attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + false); } /** @@ -496,16 +498,16 @@ final class AttributedOp { // Call started() to add a new start event object and then add the // previously removed unfinished start counts back if (proxy != null) { - startedOrPaused(event.getClientId(), proxy.getUid(), - proxy.getPackageName(), proxy.getAttributionTag(), - event.getVirtualDeviceId(), newState, event.getFlags(), - true, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(), + proxy.getDeviceId(), newState, event.getFlags(), + event.getAttributionFlags(), event.getAttributionChainId(), true, + isRunning); } else { - startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, - event.getVirtualDeviceId(), newState, event.getFlags(), true, - isRunning, event.getAttributionFlags(), - event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + Process.INVALID_UID, null, null, null, + newState, event.getFlags(), event.getAttributionFlags(), + event.getAttributionChainId(), true, isRunning); } events = isRunning ? mInProgressEvents : mPausedInProgressEvents; @@ -847,7 +849,8 @@ final class AttributedOp { InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { @@ -856,7 +859,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } if (recycled != null) { @@ -880,7 +883,8 @@ final class AttributedOp { super(maxUnusedPooledObjects); } - AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid, + AppOpsManager.OpEventProxyInfo acquire( + @IntRange(from = 0) int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String deviceId) { @@ -890,7 +894,7 @@ final class AttributedOp { return recycled; } - return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag); + return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId); } } } diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java index 3e8acee26e81..7cf2d3028aef 100644 --- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java +++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -63,7 +64,7 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH); intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS); } - context.registerReceiver(this, intentFilter); + context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED); } @Override @@ -84,7 +85,8 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { } private void launchBiometricEnrollActivity(Context context, String action) { - context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS)); + context.sendBroadcast( + new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND)); final Intent intent = new Intent(action); intent.setPackage(SETTINGS_PACKAGE); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 645a3664681b..390ee96a3417 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -37,6 +37,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; +import android.graphics.ImageFormat; import android.graphics.Rect; import android.hardware.CameraExtensionSessionStats; import android.hardware.CameraSessionStats; @@ -906,6 +907,7 @@ public class CameraServiceProxy extends SystemService int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE; boolean extensionIsAdvanced = false; + int extensionCaptureFormat = ImageFormat.UNKNOWN; if (e.mExtSessionStats != null) { switch (e.mExtSessionStats.type) { case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC: @@ -932,6 +934,9 @@ public class CameraServiceProxy extends SystemService Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type); } extensionIsAdvanced = e.mExtSessionStats.isAdvanced; + if (Flags.analytics24q3()) { + extensionCaptureFormat = e.mExtSessionStats.captureFormat; + } } int streamCount = 0; @@ -945,10 +950,13 @@ public class CameraServiceProxy extends SystemService String zoomOverrideDebug = Flags.logZoomOverrideUsage() ? ", zoomOverrideUsage " + e.mUsedZoomOverride : ""; - String mostRequestedFpsRangeDebug = Flags.analytics24q3() ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange : ""; + String extensionCaptureFormatDebug = Flags.analytics24q3() + ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat + : ""; + Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction + " clientName " + e.mClientName + ", duration " + e.getDuration() @@ -971,8 +979,10 @@ public class CameraServiceProxy extends SystemService + ", logId " + e.mLogId + ", sessionIndex " + e.mSessionIndex + ", mExtSessionStats {type " + extensionType - + " isAdvanced " + extensionIsAdvanced + "}"); + + " isAdvanced " + extensionIsAdvanced + + extensionCaptureFormatDebug + "}"); } + // Convert from CameraStreamStats to CameraStreamProto CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS]; for (int i = 0; i < MAX_STREAM_STATISTICS; i++) { @@ -1035,7 +1045,8 @@ public class CameraServiceProxy extends SystemService e.mLogId, e.mSessionIndex, extensionType, extensionIsAdvanced, e.mUsedUltraWide, e.mUsedZoomOverride, - e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper()); + e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(), + extensionCaptureFormat); } } diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 22f3bbd80a77..fa8299bd45fd 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -29,7 +29,7 @@ import java.util.Objects; */ public class DisplayControl { private static native IBinder nativeCreateDisplay(String name, boolean secure, - float requestedRefreshRate); + String uniqueId, float requestedRefreshRate); private static native void nativeDestroyDisplay(IBinder displayToken); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); private static native long[] nativeGetPhysicalDisplayIds(); @@ -43,20 +43,21 @@ public class DisplayControl { /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, 0.0f); + return nativeCreateDisplay(name, secure, "", 0.0f); } /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @param uniqueId The unique ID for the display. * @param requestedRefreshRate The requested refresh rate in frames per second. * For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded @@ -65,9 +66,10 @@ public class DisplayControl { * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure, - float requestedRefreshRate) { + String uniqueId, float requestedRefreshRate) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, requestedRefreshRate); + Objects.requireNonNull(uniqueId, "uniqueId must not be null"); + return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate); } /** @@ -79,7 +81,6 @@ public class DisplayControl { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - nativeDestroyDisplay(displayToken); } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index bcdb442c3ad3..a29e8523952d 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -92,8 +92,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { Context context, Handler handler, Listener listener, DisplayManagerFlags featureFlags) { this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) { - return DisplayControl.createDisplay(name, secure, requestedRefreshRate); + public IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate) { + return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate); } @Override @@ -126,7 +127,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; - IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId, virtualDisplayConfig.getRequestedRefreshRate()); MediaProjectionCallback mediaProjectionCallback = null; if (projection != null) { @@ -653,8 +654,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { /** * Create a virtual display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @param uniqueId The unique ID for the display. * @param requestedRefreshRate * The refresh rate, frames per second, to request on the virtual display. * It should be a divisor of refresh rate of the leader physical display @@ -663,8 +665,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { * the refresh rate of the leader physical display. * @return The token reference for the display in SurfaceFlinger. */ - IBinder createDisplay(String name, boolean secure, float requestedRefreshRate); - + IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate); + /** * Destroy a display in SurfaceFlinger. * diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 2703a2c01848..7e18d8412aae 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -158,9 +158,11 @@ final class SendKeyAction extends HdmiCecFeatureAction { mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error != SendMessageResult.SUCCESS) { + // Disable System Audio Mode, if the AVR doesn't acknowledge + // a <User Control Pressed> message. + if (error == SendMessageResult.NACK) { HdmiLogger.debug( - "AVR did not respond to <User Control Pressed>"); + "AVR did not acknowledge <User Control Pressed>"); localDevice().mService.setSystemAudioActivated(false); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 308aed66b7dc..83179914c746 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -254,7 +254,7 @@ public class InputManagerService extends IInputManager.Stub // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a // specific display. @GuardedBy("mAssociationsLock") - private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>(); // The associations of input devices to displays by descriptor. Maps from // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the @@ -1656,7 +1656,8 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "addUniqueIdAssociation()")) { @@ -1667,13 +1668,13 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); Objects.requireNonNull(displayUniqueId); synchronized (mAssociationsLock) { - mUniqueIdAssociations.put(inputPort, displayUniqueId); + mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId); } mNative.changeUniqueIdAssociation(); } @Override // Binder call - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "removeUniqueIdAssociation()")) { @@ -1682,7 +1683,7 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); synchronized (mAssociationsLock) { - mUniqueIdAssociations.remove(inputPort); + mUniqueIdAssociationsByPort.remove(inputPort); } mNative.changeUniqueIdAssociation(); } @@ -2101,9 +2102,9 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } - if (!mUniqueIdAssociations.isEmpty()) { + if (!mUniqueIdAssociationsByPort.isEmpty()) { pw.println("Unique Id Associations:"); - mUniqueIdAssociations.forEach((k, v) -> { + mUniqueIdAssociationsByPort.forEach((k, v) -> { pw.print(" port: " + k); pw.println(" uniqueId: " + v); }); @@ -2530,10 +2531,10 @@ public class InputManagerService extends IInputManager.Stub // Native callback @SuppressWarnings("unused") - private String[] getInputUniqueIdAssociations() { + private String[] getInputUniqueIdAssociationsByPort() { final Map<String, String> associations; synchronized (mAssociationsLock) { - associations = new HashMap<>(mUniqueIdAssociations); + associations = new HashMap<>(mUniqueIdAssociationsByPort); } return flatten(associations); diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java index 6eae9a4bbe22..d7d57df7f74c 100644 --- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java +++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java @@ -240,7 +240,8 @@ public class FocusEventDebugView extends RelativeLayout { return; } - post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event))); + MotionEvent motionEvent = MotionEvent.obtain(event); + post(() -> handleRotaryInput(motionEvent)); } private void handleKeyEvent(KeyEvent keyEvent) { diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 035a7485fe86..00bc7517bebd 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -40,6 +40,13 @@ final class AutofillSuggestionsController { @NonNull private final InputMethodManagerService mService; + /** + * The host input token of the input method that is currently associated with this controller. + */ + @GuardedBy("ImfLock.class") + @Nullable + private IBinder mCurHostInputToken; + private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; @NonNull final IInlineSuggestionsRequestCallback mCallback; @@ -78,6 +85,17 @@ final class AutofillSuggestionsController { } @GuardedBy("ImfLock.class") + void onResetSystemUi() { + mCurHostInputToken = null; + } + + @Nullable + @GuardedBy("ImfLock.class") + IBinder getCurHostInputToken() { + return mCurHostInputToken; + } + + @GuardedBy("ImfLock.class") void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) { @@ -124,8 +142,7 @@ final class AutofillSuggestionsController { mPendingInlineSuggestionsRequest.mCallback, mPendingInlineSuggestionsRequest.mPackageName, mService.getCurTokenDisplayIdLocked(), - mService.getCurTokenLocked(), - mService); + mService.getCurTokenLocked()); curMethod.onCreateInlineSuggestionsRequest( mPendingInlineSuggestionsRequest.mRequestInfo, callback); } else { @@ -161,22 +178,20 @@ final class AutofillSuggestionsController { * The decorator which validates the host package name in the * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. */ - private static final class InlineSuggestionsRequestCallbackDecorator + private final class InlineSuggestionsRequestCallbackDecorator extends IInlineSuggestionsRequestCallback.Stub { @NonNull private final IInlineSuggestionsRequestCallback mCallback; @NonNull private final String mImePackageName; private final int mImeDisplayId; @NonNull private final IBinder mImeToken; - @NonNull private final InputMethodManagerService mImms; InlineSuggestionsRequestCallbackDecorator( @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName, - int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) { + int displayId, @NonNull IBinder imeToken) { mCallback = callback; mImePackageName = imePackageName; mImeDisplayId = displayId; mImeToken = imeToken; - mImms = imms; } @Override @@ -195,7 +210,12 @@ final class AutofillSuggestionsController { + "]."); } request.setHostDisplayId(mImeDisplayId); - mImms.setCurHostInputToken(mImeToken, request.getHostInputToken()); + synchronized (ImfLock.class) { + final IBinder curImeToken = mService.getCurTokenLocked(); + if (mImeToken == curImeToken) { + mCurHostInputToken = request.getHostInputToken(); + } + } mCallback.onInlineSuggestionsRequest(request, callback); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 1d048cb687cb..e8543f225ef0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -150,10 +150,11 @@ public abstract class InputMethodManagerInternal { * * @param sourceInputToken the source token. * @param displayId the display hosting the IME window + * @param userId the user ID this request is about * @return {@code true} if the transfer is successful */ public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId); + int displayId, @UserIdInt int userId); /** * Reports that IME control has transferred to the given window token, or if null that @@ -287,7 +288,7 @@ public abstract class InputMethodManagerInternal { @Override public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId) { + int displayId, @UserIdInt int userId) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 25e2e3a0b979..848f74eb69d7 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -495,16 +495,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return userData.mBindingController.getSequenceNumber(); } - /** - * Increase the current binding sequence number by one. - * Reset to 1 on overflow. - */ - @GuardedBy("ImfLock.class") - private void advanceSequenceNumberLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - userData.mBindingController.advanceSequenceNumber(); - } - @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { @@ -583,16 +573,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); /** - * Set to true if our ServiceConnection is currently actively bound to - * a service (whether or not we have gotten its IBinder back yet). - */ - @GuardedBy("ImfLock.class") - private boolean hasConnectionLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.hasMainConnection(); - } - - /** * The token tracking the current IME show request that is waiting for a connection to an IME, * otherwise {@code null}. */ @@ -645,14 +625,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private int mCurTokenDisplayId = INVALID_DISPLAY; /** - * The host input token of the current active input method. - */ - @GuardedBy("ImfLock.class") - @Nullable - @MultiUserUnawareField - private IBinder mCurHostInputToken; - - /** * The display ID of the input method indicates the fallback display which returned by * {@link #computeImeDisplayIdForTarget}. */ @@ -679,16 +651,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Time that we last initiated a bind to the input method, to determine - * if we should try to disconnect and reconnect to it. - */ - @GuardedBy("ImfLock.class") - private long getLastBindTimeLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getLastBindTime(); - } - - /** * Have we called mCurMethod.bindInput()? */ @MultiUserUnawareField @@ -1840,21 +1802,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Sets current host input token. - * - * @param callerImeToken the token has been made for the current active input method - * @param hostInputToken the host input token of the current active input method - */ - void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) { - synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(callerImeToken)) { - return; - } - mCurHostInputToken = hostInputToken; - } - } - - /** * Gets enabled subtypes of the specified {@link InputMethodInfo}. * * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}. @@ -2170,7 +2117,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + @NonNull ImeOnBackInvokedDispatcher imeDispatcher, + @NonNull UserDataRepository.UserData userData) { // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. @@ -2211,7 +2159,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean connectionWasActive = mCurInputConnection != null; // Bump up the sequence for this client and attach it. - advanceSequenceNumberLocked(); + userData.mBindingController.advanceSequenceNumber(); + mCurClient = cs; mCurInputConnection = inputConnection; mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; @@ -2233,7 +2182,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (connectionIsActive != connectionWasActive) { mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); } - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final var bindingController = userData.mBindingController; // If configured, we want to avoid starting up the IME if it is not supposed to be showing @@ -2271,7 +2219,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } - InputBindResult bindResult = tryReuseConnectionLocked(cs); + InputBindResult bindResult = tryReuseConnectionLocked(userData, cs); if (bindResult != null) { return bindResult; } @@ -2383,8 +2331,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable - private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) { - if (hasConnectionLocked()) { + private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData, + @NonNull ClientState cs) { + if (userData.mBindingController.hasMainConnection()) { if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. @@ -2394,7 +2343,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false); } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); + final long lastBindTime = userData.mBindingController.getLastBindTime(); + long bindingDuration = SystemClock.uptimeMillis() - lastBindTime; if (bindingDuration < TIME_TO_RECONNECT) { // In this case we have connected to the service, but // don't yet have its interface. If it hasn't been too @@ -2527,7 +2477,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; updateSystemUiLocked(mImeWindowVis, mBackDisposition); mCurTokenDisplayId = INVALID_DISPLAY; - mCurHostInputToken = null; + mAutofillController.onResetSystemUi(); } @GuardedBy("ImfLock.class") @@ -3774,6 +3724,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. startInputByWinGainedFocus, toolType); mVisibilityStateComputer.setWindowState(windowToken, windowState); + final var userData = mUserDataRepository.getOrCreate(userId); if (sameWindowFocused && isTextEditor) { if (DEBUG) { Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client @@ -3784,7 +3735,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (editorInfo != null) { return startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, imeDispatcher); + startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, @@ -3816,7 +3767,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); didStart = true; } break; @@ -3831,7 +3782,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { - final var userData = mUserDataRepository.getOrCreate(userId); userData.mBindingController.unbindCurrentMethod(); } } @@ -3841,7 +3791,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -4476,6 +4426,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final long token = proto.start(fieldId); proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); proto.write(CUR_SEQ, getSequenceNumberLocked()); @@ -4494,7 +4445,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); proto.write(SYSTEM_READY, mSystemReady); - proto.write(HAVE_CONNECTION, hasConnectionLocked()); + proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -5620,14 +5571,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId) { + int displayId, @UserIdInt int userId) { //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; synchronized (ImfLock.class) { - if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) { + if (displayId != mCurTokenDisplayId) { + return false; + } + curHostInputToken = mAutofillController.getCurHostInputToken(); + if (curHostInputToken == null) { return false; } - curHostInputToken = mCurHostInputToken; } return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken); } @@ -5942,14 +5896,27 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. client = mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); - mImeBindingState.dump(" ", p); + mImeBindingState.dump(/* prefix= */ " ", p); final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + p.println(" mCurId=" + getCurIdLocked() + + " mHaveConnection=" + userData.mBindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + userData.mBindingController.isVisibleBound()); + + p.println(" mUserDataRepository="); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump = + u -> { + p.println(" mUserId=" + u.mUserId); + p.println(" hasMainConnection=" + + u.mBindingController.hasMainConnection()); + p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); + }; + mUserDataRepository.forAllUserData(userDataDump); + p.println(" mCurToken=" + getCurTokenLocked()); p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); - p.println(" mCurHostInputToken=" + mCurHostInputToken); + p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken()); p.println(" mCurIntent=" + getCurIntentLocked()); method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 42ec1c3ad4ed..61054a9d4de5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -11271,6 +11271,9 @@ public class NotificationManagerService extends SystemService { // Lifetime extended notifications don't need to alert on 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; + mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(), record, isAppForeground, mPostNotificationTrackerFactory.newTracker(null))); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e814f17bedef..21e4c967a995 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -10862,8 +10862,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox ? letterboxedContainerBounds : task != null ? task.getBounds() : display.getBounds(); - final int filledContainerRotation = task != null - ? task.getConfiguration().windowConfiguration.getRotation() + final boolean useActivityRotation = container.hasFixedRotationTransform() + && mIsInFixedOrientationOrAspectRatioLetterbox; + final int filledContainerRotation = useActivityRotation + ? container.getWindowConfiguration().getRotation() : display.getConfiguration().windowConfiguration.getRotation(); final Point dimensions = getRotationZeroDimensions( filledContainerBounds, filledContainerRotation); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 202e94c6ec84..237003a5fa10 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -184,6 +184,7 @@ import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; +import android.content.pm.FeatureInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -266,6 +267,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.UiThread; @@ -7400,8 +7402,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** Cache the return value for {@link #isPip2ExperimentEnabled()} */ + private static Boolean sIsPip2ExperimentEnabled = null; + + /** + * @return {@code true} if PiP2 implementation should be used. Besides the trunk stable flag, + * system property can be used to override this read only flag during development. + * It's currently limited to phone form factor, i.e., not enabled on ARC / TV. + */ static boolean isPip2ExperimentEnabled() { - return Flags.enablePip2Implementation() || SystemProperties.getBoolean( - "wm_shell.pip2", false); + if (sIsPip2ExperimentEnabled == null) { + final FeatureInfo arcFeature = SystemConfig.getInstance().getAvailableFeatures().get( + "org.chromium.arc"); + final FeatureInfo tvFeature = SystemConfig.getInstance().getAvailableFeatures().get( + FEATURE_LEANBACK); + final boolean isArc = arcFeature != null && arcFeature.version >= 0; + final boolean isTv = tvFeature != null && tvFeature.version >= 0; + sIsPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) + || (Flags.enablePip2Implementation() && !isArc && !isTv); + } + return sIsPip2ExperimentEnabled; } } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index ce1a72deb523..b5af8065726d 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.DimenRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -266,10 +267,10 @@ final class LetterboxConfiguration { private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox; // Supplier for the value in pixel to consider when detecting vertical thin letterboxing - private final IntSupplier mThinLetterboxWidthFn; + private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier; // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing - private final IntSupplier mThinLetterboxHeightFn; + private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier; // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; @@ -307,6 +308,34 @@ final class LetterboxConfiguration { // Flags dynamically updated with {@link android.provider.DeviceConfig}. @NonNull private final SynchedDeviceConfig mDeviceConfig; + // Cached version of IntSupplier customised to evaluate new dimen in pixels + // when density changes + private static class DimenPxIntSupplier implements IntSupplier { + + @NonNull + private final Context mContext; + + private final int mResourceId; + + private float mLastDensity = Float.MIN_VALUE; + private int mValue = 0; + + private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) { + mContext = context; + mResourceId = resourceId; + } + + @Override + public int getAsInt() { + final float newDensity = mContext.getResources().getDisplayMetrics().density; + if (newDensity != mLastDensity) { + mLastDensity = newDensity; + mValue = mContext.getResources().getDimensionPixelSize(mResourceId); + } + return mValue; + } + } + LetterboxConfiguration(@NonNull final Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister( () -> readLetterboxHorizontalReachabilityPositionFromConfig( @@ -364,9 +393,10 @@ final class LetterboxConfiguration { R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled); mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); - mThinLetterboxWidthFn = () -> mContext.getResources().getDimensionPixelSize( + + mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxWidthDp); - mThinLetterboxHeightFn = () -> mContext.getResources().getDimensionPixelSize( + mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxHeightDp); mLetterboxConfigurationPersister = letterboxConfigurationPersister; @@ -1144,7 +1174,7 @@ final class LetterboxConfiguration { * is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxWidthPx() { - return mThinLetterboxWidthFn.getAsInt(); + return mThinLetterboxWidthPxSupplier.getAsInt(); } /** @@ -1153,7 +1183,7 @@ final class LetterboxConfiguration { * value for (H - h) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxHeightPx() { - return mThinLetterboxHeightFn.getAsInt(); + return mThinLetterboxHeightPxSupplier.getAsInt(); } /** diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp index e65b9030195a..22c0f730ad7d 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -24,9 +24,11 @@ namespace android { static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure, - jfloat requestedRefreshRate) { - ScopedUtfChars name(env, nameObj); + jstring uniqueIdStr, jfloat requestedRefreshRate) { + const ScopedUtfChars name(env, nameObj); + const ScopedUtfChars uniqueId(env, uniqueIdStr); sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure), + std::string(uniqueId.c_str()), requestedRefreshRate)); return javaObjectForIBinder(env, token); } @@ -178,7 +180,7 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph static const JNINativeMethod sDisplayMethods[] = { // clang-format off - {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;", + {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;", (void*)nativeCreateDisplay }, {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", (void*)nativeDestroyDisplay }, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 62f5b89e701c..a01c1231b373 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -127,7 +127,7 @@ static struct { jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; - jmethodID getInputUniqueIdAssociations; + jmethodID getInputUniqueIdAssociationsByPort; jmethodID getInputUniqueIdAssociationsByDescriptor; jmethodID getDeviceTypeAssociations; jmethodID getKeyboardLayoutAssociations; @@ -272,22 +272,23 @@ public: void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name); - base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId, + base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid); status_t removeInputChannel(const sp<IBinder>& connectionToken); status_t pilferPointers(const sp<IBinder>& token); - void displayRemoved(JNIEnv* env, int32_t displayId); - void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); - void setFocusedDisplay(int32_t displayId); + void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId); + void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj); + void setFocusedDisplay(ui::LogicalDisplayId displayId); void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis); void setInputDispatchMode(bool enabled, bool frozen); void setSystemUiLightsOut(bool lightsOut); - void setPointerDisplayId(int32_t displayId); + void setPointerDisplayId(ui::LogicalDisplayId displayId); int32_t getMousePointerSpeed(); void setPointerSpeed(int32_t speed); - void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled); + void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -300,13 +301,13 @@ public: void reloadPointerIcons(); void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, - int32_t displayId, DeviceId deviceId, int32_t pointerId, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken); - void setPointerIconVisibility(int32_t displayId, bool visible); + void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible); void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); - FloatPoint getMouseCursorPosition(int32_t displayId); + FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId); void setStylusPointerIconEnabled(bool enabled); void setInputMethodConnectionIsActive(bool isActive); @@ -325,7 +326,7 @@ public: void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; bool isInputMethodConnectionActive() override; std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) override; + ui::LogicalDisplayId associatedDisplayId) override; /* --- InputDispatcherPolicyInterface implementation --- */ @@ -348,13 +349,15 @@ public: void notifyVibratorState(int32_t deviceId, bool isOn) override; bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override; void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override; - void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action, - nsecs_t when, uint32_t& policyFlags) override; + void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source, + int32_t action, nsecs_t when, + uint32_t& policyFlags) override; nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(const PointerCaptureRequest& request) override; void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; @@ -363,11 +366,13 @@ public: /* --- PointerControllerPolicyInterface implementation --- */ - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId); - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId); + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId); + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId); virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId); + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId); virtual PointerIconStyle getDefaultPointerIconId(); virtual PointerIconStyle getDefaultStylusIconId(); virtual PointerIconStyle getCustomPointerIconId(); @@ -375,7 +380,8 @@ public: /* --- PointerChoreographerPolicyInterface implementation --- */ std::shared_ptr<PointerControllerInterface> createPointerController( PointerControllerInterface::ControllerType type) override; - void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override; + void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, + const FloatPoint& position) override; /* --- InputFilterPolicyInterface implementation --- */ void notifyStickyModifierStateChanged(uint32_t modifierState, @@ -399,7 +405,7 @@ private: int32_t pointerSpeed{0}; // Displays on which its associated mice will have pointer acceleration disabled. - std::set<int32_t> displaysWithMousePointerAccelerationDisabled{}; + std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{}; // True if pointer gestures are enabled. bool pointerGesturesEnabled{true}; @@ -417,7 +423,7 @@ private: std::set<int32_t> disabledInputDevices{}; // Associated Pointer controller display. - int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT}; // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; @@ -450,7 +456,7 @@ private: void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - sp<SurfaceControl> getParentSurfaceForPointers(int displayId); + sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); template <typename T> std::unordered_map<std::string, T> readMapFromInterleavedJavaArray( @@ -459,7 +465,7 @@ private: void forEachPointerControllerLocked(std::function<void(PointerController&)> apply) REQUIRES(mLock); - PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type); + PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; @@ -490,7 +496,9 @@ void NativeInputManager::dump(std::string& dump) { toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n", - dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str()); + dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled, + streamableToString) + .c_str()); dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", @@ -552,7 +560,7 @@ base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChann } base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor( - int32_t displayId, const std::string& name, gui::Pid pid) { + ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) { ATRACE_CALL(); return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid); } @@ -616,10 +624,9 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon env->DeleteLocalRef(portAssociations); } - outConfig->uniqueIdAssociationsByPort = - readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo - .getInputUniqueIdAssociations, - "getInputUniqueIdAssociations"); + outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray< + std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort, + "getInputUniqueIdAssociationsByPort"); outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray< std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, @@ -735,7 +742,7 @@ void NativeInputManager::forEachPointerControllerLocked( } } -PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId, +PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type) { if (type == PointerIconStyle::TYPE_CUSTOM) { LOG(FATAL) << __func__ << ": Cannot load non-system icon type"; @@ -766,7 +773,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon return pc; } -void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, +void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId, const FloatPoint& position) { // Notify the Reader so that devices can be reconfigured. { // acquire lock @@ -775,7 +782,7 @@ void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, return; } mLocked.pointerDisplayId = pointerDisplayId; - ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId); + ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str()); } // release lock mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); @@ -795,7 +802,7 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged"); } -sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) { +sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) { JNIEnv* env = jniEnv(); jlong nativeSurfaceControlPtr = env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers, @@ -817,9 +824,10 @@ void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { layer = -1; } mLocked.spriteController = - std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) { - return getParentSurfaceForPointers(displayId); - }); + std::make_shared<SpriteController>(mLooper, layer, + [this](ui::LogicalDisplayId displayId) { + return getParentSurfaceForPointers(displayId); + }); // The SpriteController needs to be shared pointer because the handler callback needs to hold // a weak reference so that we can avoid racy conditions when the controller is being destroyed. mLocked.spriteController->setHandlerController(mLocked.spriteController); @@ -1021,8 +1029,7 @@ void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) { jobject tokenObj = javaObjectForIBinder(env, token); if (tokenObj) { - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, - tokenObj); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj); checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken"); } } @@ -1108,12 +1115,12 @@ void NativeInputManager::notifyVibratorState(int32_t deviceId, bool isOn) { checkAndClearExceptionFromCallback(env, "notifyVibratorState"); } -void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) { +void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().displayRemoved(displayId); } -void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, - jobject applicationHandleObj) { +void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj) { if (!applicationHandleObj) { return; } @@ -1123,7 +1130,7 @@ void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle); } -void NativeInputManager::setFocusedDisplay(int32_t displayId) { +void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().setFocusedDisplay(displayId); } @@ -1151,7 +1158,7 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { }); } -void NativeInputManager::setPointerDisplayId(int32_t displayId) { +void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) { mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId); } @@ -1176,7 +1183,8 @@ void NativeInputManager::setPointerSpeed(int32_t speed) { InputReaderConfiguration::Change::POINTER_SPEED); } -void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) { +void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, + bool enabled) { { // acquire lock std::scoped_lock _l(mLock); @@ -1186,8 +1194,8 @@ void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, b return; } - ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled), - displayId); + ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled), + displayId.toString().c_str()); if (enabled) { mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId); } else { @@ -1326,8 +1334,9 @@ void NativeInputManager::reloadPointerIcons() { } bool NativeInputManager::setPointerIcon( - std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, - DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) { + std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, + const sp<IBinder>& inputToken) { if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId, pointerId)) { LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId @@ -1339,7 +1348,7 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } -void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { +void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); } @@ -1394,7 +1403,7 @@ bool NativeInputManager::isInputMethodConnectionActive() { } std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId); } @@ -1469,9 +1478,9 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent, handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } -void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, - int32_t action, nsecs_t when, - uint32_t& policyFlags) { +void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, + uint32_t source, int32_t action, + nsecs_t when, uint32_t& policyFlags) { ATRACE_CALL(); // Policy: // - Ignore untrusted events and pass them along. @@ -1590,7 +1599,8 @@ std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinde return fallbackEvent; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) { +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } @@ -1621,13 +1631,14 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) InputReaderConfiguration::Change::POINTER_CAPTURE); } -void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { +void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW)); } -void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) { +void NativeInputManager::loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1641,7 +1652,8 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources, in void NativeInputManager::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1708,7 +1720,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); } -FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) { +FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) { return mInputManager->getChoreographer().getMouseCursorPosition(displayId); } @@ -1874,7 +1886,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint jstring nameObj, jint pid) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (displayId == ADISPLAY_ID_NONE) { + if (displayId == ui::ADISPLAY_ID_NONE.val()) { std::string message = "InputChannel used as a monitor must be associated with a display"; jniThrowRuntimeException(env, message.c_str()); return nullptr; @@ -1884,7 +1896,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint std::string name = nameChars.c_str(); base::Result<std::unique_ptr<InputChannel>> inputChannel = - im->createInputMonitor(displayId, name, gui::Pid{pid}); + im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid}); if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); @@ -1931,7 +1943,8 @@ static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolea return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid}, gui::Uid{static_cast<uid_t>(uid)}, - hasPermission, displayId); + hasPermission, + ui::LogicalDisplayId{displayId}); } static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj, @@ -2023,20 +2036,20 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->displayRemoved(env, displayId); + im->displayRemoved(env, ui::LogicalDisplayId{displayId}); } static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId, jobject applicationHandleObj) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedApplication(env, displayId, applicationHandleObj); + im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj); } static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedDisplay(displayId); + im->setFocusedDisplay(ui::LogicalDisplayId{displayId}); } static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj, @@ -2092,8 +2105,8 @@ static jboolean nativeTransferTouchOnDisplay(JNIEnv* env, jobject nativeImplObj, NativeInputManager* im = getNativeInputManager(env, nativeImplObj); if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken, - static_cast<int32_t>( - displayId))) { + ui::LogicalDisplayId{ + displayId})) { return JNI_TRUE; } else { return JNI_FALSE; @@ -2116,7 +2129,7 @@ static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject native jint displayId, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setMousePointerAccelerationEnabled(displayId, enabled); + im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled); } static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { @@ -2486,7 +2499,7 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico icon = pointerIcon.style; } - return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId, + return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId, ibinderForJavaObject(env, inputTokenObj)); } @@ -2494,13 +2507,14 @@ static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, j jboolean visible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerIconVisibility(displayId, visible); + im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible); } static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId); + return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, + ui::LogicalDisplayId{displayId}); } static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) { @@ -2512,8 +2526,9 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj, jint displayId, jboolean isEligible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, - isEligible); + im->getInputManager() + ->getDispatcher() + .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible); } static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) { @@ -2649,7 +2664,7 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) { static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerDisplayId(displayId); + im->setPointerDisplayId(ui::LogicalDisplayId{displayId}); } static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) { @@ -2667,7 +2682,7 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - const auto p = im->getMouseCursorPosition(displayId); + const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId}); const std::array<float, 2> arr = {{p.x, p.y}}; jfloatArray outArr = env->NewFloatArray(2); env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data()); @@ -2930,8 +2945,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz, "getInputPortAssociations", "()[Ljava/lang/String;"); - GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz, - "getInputUniqueIdAssociations", "()[Ljava/lang/String;"); + GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz, + "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;"); GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz, "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;"); diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index d0b290c05ee9..073396848c55 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -99,7 +99,7 @@ static bool setPowerMode(Mode mode, bool enabled) { } void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId) { + ui::LogicalDisplayId displayId) { if (gPowerManagerServiceObj) { // Throttle calls into user activity by event type. // We're a little conservative about argument checking here in case the caller @@ -124,8 +124,8 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(gPowerManagerServiceObj, - gPowerManagerServiceClassInfo.userActivityFromNative, - nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0); + gPowerManagerServiceClassInfo.userActivityFromNative, + nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0); checkAndClearExceptionFromCallback(env, "userActivityFromNative"); } } diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h index 36aaceb029c7..ed7fa7c39bd3 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.h +++ b/services/core/jni/com_android_server_power_PowerManagerService.h @@ -19,6 +19,7 @@ #include <nativehelper/JNIHelp.h> #include <powermanager/PowerManager.h> +#include <ui/LogicalDisplayId.h> #include <utils/Timers.h> #include "jni.h" @@ -26,7 +27,7 @@ namespace android { extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId); + ui::LogicalDisplayId displayId); } // namespace android diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 0c83e8e468d9..2e126f1b50ba 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -295,7 +295,7 @@ public final class CredentialManagerService } } - private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { + static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { final int resolvedUserId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 38ad5b6594b5..b86db065cfd7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -16,6 +16,8 @@ package com.android.server.credentials; +import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -30,6 +32,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; import java.util.List; +import java.util.Set; /** @@ -80,9 +83,12 @@ public final class CredentialManagerServiceImpl extends Slog.i(TAG, "newServiceInfoLocked, mInfo null, " + serviceComponent.flattenToString()); } + Set<ComponentName> primaryProviders = + getPrimaryProvidersForUserId(mMaster.getContext(), mUserId); mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, - mUserId, /*isSystemProvider=*/false); + mUserId, /*isSystemProvider=*/false, + primaryProviders.contains(serviceComponent)); return mInfo.getServiceInfo(); } diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index b1673e2c4c3c..7a026d5bd301 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -67,6 +67,9 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ private final ResultReceiver mAutofillCallback; + @Nullable + private ComponentName mPrimaryProviderComponentName = null; + public GetCandidateRequestSession( Context context, SessionLifetime sessionCallback, Object lock, int userId, int callingUid, @@ -104,8 +107,12 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ if (providerGetCandidateSessions != null) { Slog.d(TAG, "In startProviderSession - provider session created and " + "being added for: " + providerInfo.getComponentName()); - mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(), - providerGetCandidateSessions); + ComponentName componentName = providerGetCandidateSessions + .getComponentName(); + if (providerInfo.isPrimary()) { + mPrimaryProviderComponentName = componentName; + } + mProviders.put(componentName.flattenToString(), providerGetCandidateSessions); } return providerGetCandidateSessions; } @@ -138,7 +145,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ try { invokeClientCallbackSuccess(new GetCandidateCredentialsResponse( - candidateProviderDataList, intent)); + candidateProviderDataList, intent, mPrimaryProviderComponentName)); } catch (RemoteException e) { Slog.e(TAG, "Issue while responding to client with error : " + e); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 9eb7b229ad79..375fc5a0280a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -21605,9 +21605,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; } - if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode() - && isSingleUserMode && !mInjector.isChangeEnabled( - PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) { + if (Flags.headlessSingleMinTargetSdk() + && mInjector.userManagerIsHeadlessSystemUserMode() + && isSingleUserMode + && !mInjector.isChangeEnabled( + PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), + caller.getUserId())) { throw new IllegalStateException("Device admin is not targeting Android V."); } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 622e70279700..54d101a3c1cf 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -243,12 +243,12 @@ public final class ProfcollectForwardingService extends SystemService { return; } sSelfService.mIProfcollect.process(); - jobFinished(params, false); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to process profiles in background: " + e.getMessage()); } }); + jobFinished(params, false); return true; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 1666fef13685..54b2d4dd1429 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -282,7 +282,7 @@ public class DisplayManagerServiceTest { return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, + public IBinder createDisplay(String name, boolean secure, String uniqueId, float requestedRefreshRate) { return mMockDisplayToken; } diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java new file mode 100644 index 000000000000..7f2327aa4f24 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.app.AppOpsManager; +import android.companion.virtual.VirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; +import android.content.AttributionSource; +import android.os.Process; +import android.permission.PermissionManager; +import android.permission.flags.Flags; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.testing.TestableContext; +import android.virtualdevice.cts.common.VirtualDeviceRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +public class AppOpsDeviceAwareServiceTest { + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext()); + + @Rule + public VirtualDeviceRule virtualDeviceRule = + VirtualDeviceRule.withAdditionalPermissions( + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, + Manifest.permission.CREATE_VIRTUAL_DEVICE, + Manifest.permission.GET_APP_OPS_STATS); + + private static final String ATTRIBUTION_TAG_1 = "attributionTag1"; + private static final String ATTRIBUTION_TAG_2 = "attributionTag2"; + private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private final PermissionManager mPermissionManager = + mContext.getSystemService(PermissionManager.class); + + private VirtualDeviceManager.VirtualDevice mVirtualDevice; + + @Before + public void setUp() { + mVirtualDevice = + virtualDeviceRule.createManagedVirtualDevice( + new VirtualDeviceParams.Builder() + .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM) + .build()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + mVirtualDevice.getPersistentDeviceId()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnDefaultDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT); + assertThat(proxyInfo.getDeviceId()) + .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnRemoteDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId()); + assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId()); + } + + private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) { + AttributionSource proxiedAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_2) + .setDeviceId(mVirtualDevice.getDeviceId()) + .build(); + + AttributionSource proxyAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_1) + .setDeviceId(proxyAppDeviceId) + .setNextAttributionSource(proxiedAttributionSource) + .build(); + + int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false); + assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED); + + List<AppOpsManager.PackageOps> packagesOps = + mAppOpsManager.getPackagesForOps( + new String[] {AppOpsManager.OPSTR_CAMERA}, + mVirtualDevice.getPersistentDeviceId()); + + AppOpsManager.PackageOps packageOps = + packagesOps.stream() + .filter( + pkg -> + Objects.equals( + pkg.getPackageName(), mContext.getOpPackageName())) + .findFirst() + .orElseThrow(); + + AppOpsManager.OpEntry opEntry = + packageOps.getOps().stream() + .filter(op -> op.getOp() == OP_CAMERA) + .findFirst() + .orElseThrow(); + + return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java index 0716a5c5561d..3698d6fb6b76 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.UserHandle; @@ -75,13 +76,15 @@ public class BiometricDanglingReceiverTest { @Test public void testFingerprintRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test public void testFaceRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java index b33a8aa28544..00c8ed188d1e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java @@ -53,7 +53,7 @@ class InputManagerMockHelper { private IInputDevicesChangedListener mDevicesChangedListener; private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping = new HashMap<>(); - private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation = + private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort = new HashMap<>(); InputManagerMockHelper(TestableLooper testableLooper, @@ -79,11 +79,11 @@ class InputManagerMockHelper { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); doAnswer(inv -> mDevices.get(inv.getArgument(0))) .when(mIInputManagerMock).getInputDevice(anyInt()); - doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), - inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation( + doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0), + inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort( anyString(), anyString()); - doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when( - mIInputManagerMock).removeUniqueIdAssociation(anyString()); + doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when( + mIInputManagerMock).removeUniqueIdAssociationByPort(anyString()); // Set a new instance of InputManager for testing that uses the IInputManager mock as the // interface to the server. @@ -113,7 +113,7 @@ class InputManagerMockHelper { .setDescriptor(phys) .setExternal(true) .setAssociatedDisplayId( - mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys), + mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys), Display.INVALID_DISPLAY)) .build(); 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 5e2fe6a080eb..2d672b89662f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5969,6 +5969,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + assertThat(captor.getValue().getNotification().flags + & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); assertThat(captor.getValue().shouldPostSilently()).isTrue(); } @@ -8798,6 +8800,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + assertThat(captor.getValue().getNotification().flags + & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); assertThat(captor.getValue().shouldPostSilently()).isTrue(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index b90fa21cb2b1..79e401c238bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -18,15 +18,17 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; - import static com.android.server.wm.testing.Assert.assertThrows; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -35,11 +37,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; -import com.android.server.wm.testing.Assert; +import com.android.internal.R; import org.junit.Before; import org.junit.Test; @@ -277,7 +281,7 @@ public class LetterboxConfigurationTest { } @Test - public void test_lettterboxPositionWhenReachabilityEnabledIsSet() { + public void test_letterboxPositionWhenReachabilityEnabledIsSet() { // Check that horizontal reachability is set with correct arguments mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); @@ -344,4 +348,48 @@ public class LetterboxConfigurationTest { mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f); mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1); } + + @Test + public void test_evaluateThinLetterboxWhenDensityChanges() { + final Resources rs = mock(Resources.class); + final DisplayMetrics dm = mock(DisplayMetrics.class); + final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class); + spyOn(mContext); + when(rs.getDisplayMetrics()).thenReturn(dm); + when(mContext.getResources()).thenReturn(rs); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp); + + // Verify the values are the expected ones + dm.density = 100; + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final int thinWidthPx = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx); + assertEquals(200, thinHeightPx); + + // We change the values in the resources but not the update condition (density) and the + // result should not change + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(300); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(400); + final int thinWidthPx2 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx2 = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx2); + assertEquals(200, thinHeightPx2); + + // We update the condition (density) so the new resource values should be read + dm.density = 150; + final int thinWidthPx3 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx3 = configuration.getThinLetterboxHeightPx(); + assertEquals(300, thinWidthPx3); + assertEquals(400, thinHeightPx3); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index afa669807c2e..d9fd312423d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -1894,11 +1894,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_createTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); - final TaskFragment tf = createTaskFragment(task); final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); @@ -1913,9 +1909,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_removeTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); final TaskFragment tf = createTaskFragment(task); diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 709f58d583a0..3c72498082e4 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -311,9 +311,8 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) } - // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations @Test - fun addUniqueIdAssociation_verifyAssociations() { + fun addUniqueIdAssociationByPort_verifyAssociations() { // Overall goal is to have 2 displays and verify that events from the InputDevice are // sent only to the view that is on the associated display. // So, associate the InputDevice with display 1, then send and verify KeyEvents. @@ -334,7 +333,7 @@ class InputManagerServiceTests { val inputDevice = createInputDevice() // Associate input device with display - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[0].display.displayId.toString() ) @@ -358,10 +357,10 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) // Remove association - service.removeUniqueIdAssociation(inputDevice.name) + service.removeUniqueIdAssociationByPort(inputDevice.name) // Associate with Display 2 - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[1].display.displayId.toString() ) diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index 3d5a0f7a239f..395f744ef043 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.net.wifi.flags.Flags; import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; import android.os.Bundle; import android.os.Parcel; @@ -170,7 +171,7 @@ public final class NetworkProviderInfo implements Parcelable { * @return Returns the Builder object. */ @NonNull - @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") + @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS) public Builder setBatteryCharging(boolean isBatteryCharging) { mIsBatteryCharging = isBatteryCharging; return this; @@ -285,7 +286,7 @@ public final class NetworkProviderInfo implements Parcelable { * * @return Returns true if the battery of the remote device is charging. */ - @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") + @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS) public boolean isBatteryCharging() { return mIsBatteryCharging; } |