Merge "Switch to -nodeps flavor of mockito-kotlin" into main
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 02e8eec..e680103 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -262,6 +262,7 @@
$(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 255ec92..74b34fb 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -246,12 +246,20 @@
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 @@
"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 eea3b84..373355a 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 @@
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 @@
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 f3a1fff..01abdb6 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 @@
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 852b00b..d5a58d1 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 @@
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 eabe1f1..ba1bcc8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10342,7 +10342,7 @@
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 @@
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 e225c5b0..211fc96 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1774,16 +1774,16 @@
}
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 c4fe061..6cc71e5 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -975,14 +975,14 @@
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 @@
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 caaaf51..d4812dd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -50,6 +50,7 @@
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 @@
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 @@
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 c5ad110..c624c43 100644
--- a/core/java/android/app/RemoteServiceException.java
+++ b/core/java/android/app/RemoteServiceException.java
@@ -71,6 +71,33 @@
}
/**
+ * 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 726064e..aaddaa6 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1198,8 +1198,7 @@
* 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 9cf83b9..bb24fd1 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -60,3 +60,13 @@
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 9ef8b38..46c9e78 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -21,6 +21,7 @@
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 @@
* 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 @@
*
* <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 @@
}
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 6da96c1..3d6ec19 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -313,3 +313,23 @@
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 9f09d04..5a13255 100644
--- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
+++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl
@@ -48,6 +48,8 @@
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 @@
* 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 7f01a82..37f419d 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -462,6 +462,20 @@
}
/**
+ * @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 3d8ccaa..c70eff4 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,8 @@
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 @@
@NonNull
private final List<GetCredentialProviderData> mCandidateProviderDataList;
+ @Nullable
+ private final ComponentName mPrimaryProviderComponentName;
+
@NonNull
private final Intent mIntent;
@@ -48,13 +53,15 @@
@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 @@
}
/**
+ * Returns the primary provider component name.
+ *
+ * @hide
+ */
+ @Nullable
+ public ComponentName getPrimaryProviderComponentName() {
+ return mPrimaryProviderComponentName;
+ }
+
+ /**
* Returns candidate provider data list.
*
* @hide
@@ -83,12 +100,15 @@
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 6d9b51cb..2e1e90c 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -200,6 +200,8 @@
supportedCaptureSizes.put(format, supportedSizes);
}
}
+
+ int captureFormat = ImageFormat.UNKNOWN;
Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface(
config.getOutputConfigurations(), supportedCaptureSizes);
OutputConfiguration burstCaptureOutputConfig = null;
@@ -210,6 +212,12 @@
}
}
suitableSurfaceCount++;
+
+ if (Flags.analytics24q3()) {
+ CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+ CameraExtensionUtils.querySurface(burstCaptureSurface);
+ captureFormat = burstCaptureSurfaceInfo.mFormat;
+ }
}
if (suitableSurfaceCount != config.getOutputConfigurations().size()) {
@@ -249,6 +257,9 @@
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 3ae3199..a4ae398 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -200,10 +200,18 @@
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 @@
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 3050a51..c75e418 100644
--- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -70,6 +70,23 @@
}
/**
+ * 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 @@
+ " 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 8f78032..45b316a 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -171,9 +171,9 @@
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 57004bc..7527aa7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1094,10 +1094,9 @@
*/
@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 @@
*/
@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 cb3af2b..fcd5a3e 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1445,22 +1445,23 @@
}
/**
- * @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 9a63394..49ab15a 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -429,10 +429,9 @@
"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/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 92f2c32..3cd705a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -86,7 +86,8 @@
@NonNull Context context,
@NonNull ComponentName serviceComponent,
int userId,
- boolean isSystemProvider)
+ boolean isSystemProvider,
+ boolean isPrimary)
throws PackageManager.NameNotFoundException {
return create(
context,
@@ -94,7 +95,7 @@
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 db665a9..c4becea 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -1392,10 +1392,6 @@
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 bfe4e6f..9cc4191 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1978,6 +1978,13 @@
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 @@
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 1a9322e..2039b4d 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -49,6 +49,7 @@
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 c5114b9..fb3e083 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -720,6 +720,20 @@
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 fa9458d..56e5bcf 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,6 +62,17 @@
}
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 fb1c331..78dd3b1 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_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 @@
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/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 14388a6..5b0e6b9 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 @@
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 @@
@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 a683738..c708da9 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 @@
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 @@
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 @@
}
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 @@
// 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 @@
}
boolean isInPictureInPicture() {
- return isInPictureInPicture(mConfiguration);
+ return isInPictureInPicture(mInfo.getConfiguration());
}
private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
@@ -237,7 +226,7 @@
@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 ceeed88..ea30af5 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 @@
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 7bceb2c..be88b34 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 @@
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 @@
mDismissView.hide();
}
- mIsDraggingStack = false;
- mMagnetizedObject = null;
+ onDraggingEnded();
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
@@ -1096,6 +1096,7 @@
} else {
maybeShowStackEdu();
}
+ onDraggingEnded();
});
animate()
@@ -1153,6 +1154,14 @@
}
/**
+ * 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 6980c6f..ec3c601 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 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 @@
BubbleBarLocation.class);
expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(),
Point.class);
+ showOverflowChanged = parcel.readBoolean();
+ showOverflow = parcel.readBoolean();
}
/**
@@ -107,7 +111,8 @@
|| suppressedBubbleKey != null
|| unsupressedBubbleKey != null
|| !currentBubbleList.isEmpty()
- || bubbleBarLocation != null;
+ || bubbleBarLocation != null
+ || showOverflowChanged;
}
@NonNull
@@ -128,6 +133,8 @@
+ " currentBubbleList=" + currentBubbleList
+ " bubbleBarLocation=" + bubbleBarLocation
+ " expandedViewDropTargetSize=" + expandedViewDropTargetSize
+ + " showOverflowChanged=" + showOverflowChanged
+ + " showOverflow=" + showOverflow
+ " }";
}
@@ -152,6 +159,8 @@
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 ea86c79..dba0a98 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 @@
}
}
+ 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/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 1a5b938..31c9db7 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 bd0e35a..0000000
--- 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/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 50ebdd5..0da32bd 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 @@
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.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.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 @@
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 @@
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 @@
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 @@
}
}
}
- 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 @@
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 @@
getDefaultIcon()
} else {
entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey]
- ?: getDefaultIcon()
+ ?: 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)
- }
+ val displayName = primaryEntry.displayName
+ val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
+ displayName != null
+ ) {
+ displayName
+ } else {
+ primaryEntry.userName
}
- var dropdownPresentation: RemoteViews? = null
- if (i < maxDatasetDisplayLimit) {
- dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry, /*isFirstEntry= */ i == 0,
- /*isLastEntry= */ (totalEntryCount - i == 1))
+ val subtitle = if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true
+ ) {
+ primaryEntry.userName
+ } else {
+ null
}
-
- val dataSetBuilder = Dataset.Builder()
- val presentationBuilder = Presentations.Builder()
- if (dropdownPresentation != null) {
- presentationBuilder.setMenuPresentation(dropdownPresentation)
- }
- if (inlinePresentation != null) {
- presentationBuilder.setInlinePresentation(inlinePresentation)
- }
-
+ 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 createInlinePresentation(
- primaryEntry: CredentialEntryInfo,
- pendingIntent: PendingIntent,
- icon: Icon,
- spec: InlinePresentationSpec,
- duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>
- ): InlinePresentation {
- val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY &&
- primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
+ 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
+ )
+ }
+ }
+ 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(
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ title: String,
+ subtitle: String?,
+ isActionEntry: Boolean
+ ): InlinePresentation {
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 @@
}
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 @@
* }
*/
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 @@
)
}
+ 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 @@
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 @@
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
traverseNodeForRequest(
windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints, sessionId)
+ credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest)
}
}
@@ -580,7 +759,8 @@
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 @@
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 @@
}
children.forEach { childNode: AssistStructure.ViewNode ->
- traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints, sessionId)
+ traverseNodeForRequest(
+ childNode, cmRequests, responseClientState, traversedViewNodes,
+ credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest
+ )
}
}
@@ -611,7 +795,8 @@
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 @@
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 7bb08d2..98e1690 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 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 @@
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 @@
* 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 @@
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 8e78861..19f5a99 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 @@
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 30bec77..c2a83b1 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.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 @@
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 @@
boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio
&& isConnectedHapClientDevice();
if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) {
- return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary);
+ stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary);
}
}
}
@@ -1498,6 +1493,8 @@
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 @@
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 @@
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;
+ if (isLeftDeviceConnected && isRightDeviceConnected) {
+ return (shouldShowLeftBattery && shouldShowRightBattery)
+ ? R.string.bluetooth_active_battery_level_untethered
+ : R.string.bluetooth_hearing_aid_left_and_right_active;
+ }
+ if (isLeftDeviceConnected) {
+ return shouldShowLeftBattery
+ ? R.string.bluetooth_active_battery_level_untethered_left
+ : R.string.bluetooth_hearing_aid_left_active;
+ }
+ if (isRightDeviceConnected) {
+ return shouldShowRightBattery
+ ? R.string.bluetooth_active_battery_level_untethered_right
+ : R.string.bluetooth_hearing_aid_right_active;
}
- 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 (leftBattery < 0 && leftDevice != null) {
- leftBattery = leftDevice.getBatteryLevel();
- }
- 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);
- }
- }
-
- 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 @@
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 b4bd482..b9bf9ca 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 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
TWS_BATTERY_RIGHT.getBytes());
assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo(
- "Left 15% Right 25%");
+ "Left: 15% battery Right: 25% battery");
}
@Test
@@ -1741,16 +1789,6 @@
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 @@
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 14ebc39..755fe2a 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -4,6 +4,16 @@
# 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 80398cd..e69ac0a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -857,6 +857,16 @@
}
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 @@
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 3227611..6fe5cef 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.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 @@
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
- isDraggingToRemove = isDraggingToRemove,
setToolbarSize = { toolbarSize = it },
setRemoveButtonCoordinates = { removeButtonCoordinates = it },
onEditDone = onEditDone,
@@ -577,7 +577,6 @@
*/
@Composable
private fun Toolbar(
- isDraggingToRemove: Boolean,
removeEnabled: Boolean,
onRemoveClicked: () -> Unit,
setToolbarSize: (toolbarSize: IntSize) -> Unit,
@@ -591,7 +590,7 @@
label = "RemoveButtonAlphaAnimation"
)
- Row(
+ Box(
modifier =
Modifier.fillMaxWidth()
.padding(
@@ -600,65 +599,54 @@
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),
- )
+
+ if (!removeEnabled) {
+ Button(
+ modifier = Modifier.align(Alignment.CenterStart),
+ 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) }
- ) {
- RemoveButtonContent(spacerModifier)
- }
- } else {
- OutlinedButton(
- enabled = removeEnabled,
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 @@
@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/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
index 556bbbe..c37d626 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
@@ -18,9 +18,11 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
import com.android.systemui.media.controls.ui.view.MediaHost
@@ -33,20 +35,15 @@
constructor(
private val mediaCarouselController: MediaCarouselController,
@param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
- private val mediaCarouselViewModel: MediaCarouselViewModel,
+ private val keyguardMediaViewModel: KeyguardMediaViewModel,
) {
- private fun isVisible(): Boolean {
- if (mediaCarouselController.mediaFrame == null) {
- return false
- }
- return mediaCarouselViewModel.isMediaVisible
- }
-
@Composable
fun SceneScope.KeyguardMediaCarousel() {
+ val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsState()
+
MediaCarousel(
- isVisible = ::isVisible,
+ isVisible = isMediaVisible,
mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
carouselController = mediaCarouselController,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 241c171..581f3a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -42,12 +42,12 @@
@Composable
fun SceneScope.MediaCarousel(
- isVisible: () -> Boolean,
+ isVisible: Boolean,
mediaHost: MediaHost,
modifier: Modifier = Modifier,
carouselController: MediaCarouselController,
) {
- if (!isVisible()) {
+ if (!isVisible) {
return
}
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 5f84dd4..2f241ce 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 @@
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 30b6c6c..7c415cc 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
@@ -288,7 +288,8 @@
}
Column(
- modifier = shadeHeaderAndQuickSettingsModifier,
+ modifier =
+ shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"),
) {
when (LocalWindowSizeClass.current.widthSizeClass) {
WindowWidthSizeClass.Compact ->
@@ -336,11 +337,13 @@
viewModel.qsSceneAdapter,
{ viewModel.qsSceneAdapter.qsHeight },
isSplitShade = false,
- modifier = Modifier.sysuiResTag("expanded_qs_scroll_view")
+ modifier = Modifier.sysuiResTag("quick_settings_panel")
)
+ val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+
MediaCarousel(
- isVisible = viewModel::isMediaVisible,
+ isVisible = isMediaVisible,
mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
carouselController = mediaCarouselController,
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 10fe0cab..d2e7d7d 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
@@ -221,6 +221,7 @@
canOverflow = false
)
val isClickable by viewModel.isClickable.collectAsState()
+ val isMediaVisible by viewModel.isMediaVisible.collectAsState()
val shouldPunchHoleBehindScrim =
layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -274,7 +275,7 @@
}
MediaCarousel(
- isVisible = viewModel::isMediaVisible,
+ isVisible = isMediaVisible,
mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
carouselController = mediaCarouselController,
@@ -381,6 +382,8 @@
viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
+ val isMediaVisible by viewModel.isMediaVisible.collectAsState()
+
val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha }
Box(
@@ -455,7 +458,7 @@
}
MediaCarousel(
- isVisible = viewModel::isMediaVisible,
+ isVisible = isMediaVisible,
mediaHost = mediaHost,
modifier = Modifier.fillMaxWidth(),
carouselController = mediaCarouselController,
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 92d5c26..d924d88 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 @@
*/
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 def63ec..bfed33c 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 @@
powerInteractor = kosmos.powerInteractor,
keyguardInteractor = kosmos.keyguardInteractor,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
+ communalInteractor = kosmos.communalInteractor,
dreamManager = dreamManager,
bgScope = kosmos.applicationCoroutineScope,
)
@@ -84,6 +89,32 @@
}
@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/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
index feb7298..7292985 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
@@ -64,7 +63,7 @@
private val kosmos = testKosmos()
- private lateinit var underTest: HomeControlsComponentInteractor
+ private val underTest by lazy { kosmos.homeControlsComponentInteractor }
@Before
fun setUp() =
@@ -73,8 +72,7 @@
fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
-
- underTest = homeControlsComponentInteractor
+ Unit
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index da60c18..dfc004a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -30,10 +30,10 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -47,11 +47,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileRepository =
with(kosmos) {
CustomTileRepositoryImpl(
- tileSpec,
+ customTileSpec,
customTileStatePersister,
packageManagerAdapterFacade.packageManagerAdapter,
testScope.testScheduler,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
index a5c5544..a29289a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -38,8 +38,8 @@
import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.customTileRepository
import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.data.repository.userRepository
@@ -60,12 +60,12 @@
private val kosmos =
testKosmos().apply {
componentName = TEST_COMPONENT
- tileSpec = TileSpec.create(componentName)
+ customTileSpec = TileSpec.create(componentName)
}
private val underTest =
with(kosmos) {
CustomTileDataInteractor(
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
defaultsRepository = customTileDefaultsRepository,
serviceInteractor = customTileServiceInteractor,
customTileInteractor = customTileInteractor,
@@ -180,7 +180,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_1.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
)
@@ -198,7 +198,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_1.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Error,
)
@@ -216,7 +216,7 @@
setup()
customTileDefaultsRepository.putDefaults(
TEST_USER_2.userHandle,
- tileSpec.componentName,
+ customTileSpec.componentName,
CustomTileDefaults.Error,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 9546a32..33299d9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -31,9 +31,9 @@
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,12 +50,12 @@
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
defaultsRepository = customTileDefaultsRepository,
customTileRepository = customTileRepository,
tileScope = testScope.backgroundScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index a2127a4..3972938 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -33,9 +33,9 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -51,7 +51,8 @@
class CustomTileMapperTest : SysuiTestCase() {
private val uriGrantsManager: IUriGrantsManager = mock {}
- private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+ private val kosmos =
+ testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
private val underTest by lazy {
CustomTileMapper(
context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
@@ -202,7 +203,7 @@
) =
CustomTileDataModel(
UserHandle.of(1),
- tileSpec.componentName,
+ customTileSpec.componentName,
Tile().apply {
state = tileState
label = "test label"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
index c709f16..72e5766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -44,9 +44,9 @@
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
-import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
@@ -68,7 +68,7 @@
private val kosmos =
testKosmos().apply {
componentName = TEST_COMPONENT
- tileSpec = TileSpec.create(componentName)
+ customTileSpec = TileSpec.create(componentName)
testCase = this@CustomTileUserActionInteractorTest
}
@@ -79,7 +79,7 @@
mock {
whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
},
- tileSpec = tileSpec,
+ tileSpec = customTileSpec,
qsTileLogger = qsTileLogger,
windowManager = windowManagerFacade.windowManager,
displayTracker = mock {},
@@ -227,7 +227,7 @@
private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
private fun Kosmos.customTileModel(
- componentName: ComponentName = tileSpec.componentName,
+ componentName: ComponentName = customTileSpec.componentName,
activityLaunchForClick: PendingIntent? = null,
tileState: Int = 111,
) =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 179ba42..0b55bef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -35,7 +35,10 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
@@ -100,7 +103,7 @@
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
sceneBackInteractor = sceneBackInteractor,
- mediaDataManager = mediaDataManager,
+ mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
)
}
@@ -236,20 +239,34 @@
}
@Test
- fun hasMedia_mediaVisible() {
+ fun addAndRemoveMedia_mediaVisibilityIsUpdated() =
testScope.runTest {
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+ val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+ val userMedia = MediaData(active = true)
- assertThat(underTest.isMediaVisible()).isTrue()
+ assertThat(isMediaVisible).isFalse()
+
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+ assertThat(isMediaVisible).isTrue()
+
+ kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+ assertThat(isMediaVisible).isFalse()
}
- }
@Test
- fun doesNotHaveMedia_mediaNotVisible() {
+ fun addInactiveMedia_mediaVisibilityIsUpdated() =
testScope.runTest {
- whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
+ val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+ val userMedia = MediaData(active = false)
- assertThat(underTest.isMediaVisible()).isFalse()
+ assertThat(isMediaVisible).isFalse()
+
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+
+ assertThat(isMediaVisible).isTrue()
}
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 4cb8bf8..9e7e766 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -209,7 +210,7 @@
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- mediaDataManager = mediaDataManager,
+ mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
shadeInteractor = kosmos.shadeInteractor,
footerActionsController = kosmos.footerActionsController,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 2439217..4622f0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -32,7 +32,10 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.data.repository.mediaFilterRepository
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
@@ -50,7 +53,6 @@
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -94,7 +96,7 @@
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- mediaDataManager = mediaDataManager,
+ mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
shadeInteractor = kosmos.shadeInteractor,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
footerActionsController = kosmos.footerActionsController,
@@ -222,19 +224,20 @@
}
@Test
- fun hasActiveMedia_mediaVisible() =
+ fun addAndRemoveMedia_mediaVisibilityisUpdated() =
testScope.runTest {
- whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true)
+ val isMediaVisible by collectLastValue(underTest.isMediaVisible)
+ val userMedia = MediaData(active = true)
- assertThat(underTest.isMediaVisible()).isTrue()
- }
+ assertThat(isMediaVisible).isFalse()
- @Test
- fun doesNotHaveActiveMedia_mediaNotVisible() =
- testScope.runTest {
- whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false)
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
- assertThat(underTest.isMediaVisible()).isFalse()
+ assertThat(isMediaVisible).isTrue()
+
+ kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId)
+
+ assertThat(isMediaVisible).isFalse()
}
@Test
diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
index cf7a730..a30a122 100644
--- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
+++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml
@@ -13,12 +13,16 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
- <!-- Since this layer list has just one layer, we can remove it and replace with the inner
- layer drawable. However this should only be done when the flag
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/qs_tile_ripple_color">
+ <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes
+ performance regression, see: b/339412453.
+ Since this ripple has just one layer inside, we can try to remove that extra "background"
+ layer. However this should only be done when the flag
com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable
fully replaces the previous one to ensure consistency with code sections searching for
- specific ids in drawable hierarchy -->
+ specific ids in drawable hierarchy
+ -->
<item
android:id="@id/background">
<layer-list>
@@ -48,4 +52,4 @@
</item>
</layer-list>
</item>
-</layer-list>
\ No newline at end of file
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a1daebd..5857692 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 422f02f..8979ef1 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.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 @@
(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 @@
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
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index a97e2dc..8782962 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 177d933..35c2024 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.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.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 @@
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 @@
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 @@
@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 @@
@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 5ba0b2d..9ba41ef 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.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 @@
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 9ad3f43..f659ff0 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 @@
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 @@
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 @@
_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 b7c0fa8..f8fb7bb 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 @@
* 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 @@
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 @@
_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 45816c1..deb47d3 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 @@
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 @@
/** 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 @@
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 @@
override fun useCredentialsForAuthentication(
promptInfo: PromptInfo,
- @Utils.CredentialType kind: Int,
+ kind: PromptKind,
userId: Int,
challenge: Long,
opPackageName: String,
@@ -200,7 +192,7 @@
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
- kind = kind.asBiometricPromptCredential(),
+ kind = kind,
opPackageName = opPackageName,
)
}
@@ -209,13 +201,3 @@
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 9e7fb4e..153b7aa 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.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.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 @@
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 @@
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/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
index 40d7440..b27fcfc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -126,7 +126,7 @@
private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
getSharedPrefsForUser(user)
- .observe(CTA_DISMISSED_STATE)
+ .observe()
// Emit at the start of collection to ensure we get an initial value
.onStart { emit(Unit) }
.map { getCtaDismissedState() }
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 6b4cf79..06c8396 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 @@
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/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index 7c2dae3..060a331 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -25,10 +25,10 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
class AuthorizedPanelsRepositoryImpl
@Inject
@@ -40,7 +40,7 @@
override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> {
val prefs = instantiateSharedPrefs(user)
- return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) }
+ return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) }
}
override fun getAuthorizedPanels(): Set<String> {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
index 9be04940..691ec76 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -26,12 +26,12 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.kotlin.SharedPreferencesExt.observe
+import com.android.systemui.util.kotlin.emitOnStart
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -63,8 +63,8 @@
): Flow<SelectedComponentRepository.SelectedComponent?> {
val prefs = getSharedPreferencesForUser(userHandle.identifier)
return prefs
- .observe(PREF_COMPONENT)
- .onStart { emit(Unit) }
+ .observe()
+ .emitOnStart()
.map { getSelectedComponent(userHandle) }
.flowOn(bgDispatcher)
}
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 0fd6887..8c3de4b 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 @@
dozeFalsingManagerAdapter,
dozeTriggers,
dozeUi,
- dozeScreenState,
dozeScreenBrightness,
+ dozeScreenState,
dozeWallpaperState,
dozeDockHandler,
dozeAuthRemover,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
index 74452d1..2034138 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt
@@ -21,7 +21,6 @@
import android.content.ComponentName
import android.os.PowerManager
import android.os.UserHandle
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.domain.interactor.PackageChangeInteractor
import com.android.systemui.common.shared.model.PackageChangeModel
import com.android.systemui.controls.ControlsServiceInfo
@@ -36,6 +35,7 @@
import com.android.systemui.util.kotlin.pairwiseBy
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.time.SystemClock
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds
@@ -132,7 +132,7 @@
?: panels.firstOrNull()
item?.panelActivity
}
- .stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(bgScope, SharingStarted.Eagerly, null)
private val taskFragmentFinished =
MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
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 9b07675f..5a28f711 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.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 @@
}
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 @@
}
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 a306954..4d73774 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.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 @@
* 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 63294f7..e738ea4 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.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 @@
}
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 @@
}
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 7961b45..c952e08 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.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 @@
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 @@
}
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 @@
}
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 da4e989d..faab033 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.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 @@
) {
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 2b3732f..c2c095b 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.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 @@
) {
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 dad2d96..56261e0 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.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.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 @@
}
private fun listenForLockscreenToPrimaryBouncer() {
- if (SceneContainerFlag.isEnabled) return
scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing }
@@ -177,7 +174,6 @@
/* 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 @@
}
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 @@
* 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 9559250..2a7178f 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 @@
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.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 @@
.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 @@
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 @@
}
}
+ 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 53a0c32..181a551 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.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 @@
}
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 @@
}
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 @@
}
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 fcf67d5..197221a 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.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 @@
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 2c05d49..a18579d 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.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 @@
* 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 dc35e43..bb2eeb7 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 @@
fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor,
notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
- sceneInteractor: SceneInteractor,
) {
private val defaultSurfaceBehindVisibility =
transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
@@ -109,42 +103,21 @@
* 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 @@
* 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
+ 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()
- } 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)
- }
- }
- .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/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
index bf22563..e68e465 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
+class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) {
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index e3ba36f..0696fbe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -16,8 +16,17 @@
package com.android.systemui.qs.panels.dagger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl
+import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor
+import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
@@ -31,8 +40,23 @@
interface PanelsModule {
@Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository
+ @Binds
+ fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
+
+ @Binds
+ fun bindDefaultGridConsistencyInteractor(
+ impl: NoopGridConsistencyInteractor
+ ): GridTypeConsistencyInteractor
+
companion object {
@Provides
+ @SysUISingleton
+ @GridConsistencyLog
+ fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("GridConsistencyLog", 50)
+ }
+
+ @Provides
@IntoSet
fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
return Pair(InfiniteGridLayoutType, gridLayout)
@@ -44,5 +68,20 @@
): Map<GridLayoutType, GridLayout> {
return entries.toMap()
}
+
+ @Provides
+ @IntoSet
+ fun provideGridConsistencyInteractor(
+ consistencyInteractor: InfiniteGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(InfiniteGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
+ fun provideGridConsistencyInteractorMap(
+ entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
+ ): Map<GridLayoutType, GridTypeConsistencyInteractor> {
+ return entries.toMap()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 02dd33e..542d0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -20,10 +20,16 @@
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+interface GridLayoutTypeRepository {
+ val layout: StateFlow<GridLayoutType>
+}
@SysUISingleton
-class GridLayoutTypeRepository @Inject constructor() {
- val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType)
+class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
+ private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
+ override val layout = _layout.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index 92f87e7..e581bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,20 +19,20 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
interface IconTilesRepository {
- val iconTilesSpecs: Flow<Set<TileSpec>>
+ val iconTilesSpecs: StateFlow<Set<TileSpec>>
}
@SysUISingleton
class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
- /** Set of toggleable tiles that are suitable for being shown as an icon. */
- override val iconTilesSpecs: Flow<Set<TileSpec>> =
- flowOf(
+ private val _iconTilesSpecs =
+ MutableStateFlow(
setOf(
TileSpec.create("airplane"),
TileSpec.create("battery"),
@@ -50,4 +50,7 @@
TileSpec.create("rotation")
)
)
+
+ /** Set of toggleable tiles that are suitable for being shown as an icon. */
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
new file mode 100644
index 0000000..43ccdf66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class InfiniteGridSizeRepository @Inject constructor() {
+ // Number of columns in the narrowest state for consistency
+ private val _columns = MutableStateFlow(4)
+ val columns: StateFlow<Int> = _columns.asStateFlow()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
new file mode 100644
index 0000000..7732092
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class GridConsistencyInteractor
+@Inject
+constructor(
+ private val gridLayoutTypeInteractor: GridLayoutTypeInteractor,
+ private val currentTilesInteractor: CurrentTilesInteractor,
+ private val consistencyInteractors:
+ Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>,
+ private val defaultConsistencyInteractor: GridTypeConsistencyInteractor,
+ @GridConsistencyLog private val logBuffer: LogBuffer,
+ @Application private val applicationScope: CoroutineScope,
+) {
+ fun start() {
+ applicationScope.launch {
+ gridLayoutTypeInteractor.layout.collectLatest { type ->
+ val consistencyInteractor =
+ consistencyInteractors[type] ?: defaultConsistencyInteractor
+ currentTilesInteractor.currentTiles
+ .map { tiles -> tiles.map { it.spec } }
+ .collectLatest { tiles ->
+ val newTiles = consistencyInteractor.reconcileTiles(tiles)
+ if (newTiles != tiles) {
+ currentTilesInteractor.setTiles(newTiles)
+ logChange(newTiles)
+ }
+ }
+ }
+ }
+ }
+
+ private fun logChange(tiles: List<TileSpec>) {
+ logBuffer.log(
+ LOG_BUFFER_CURRENT_TILES_CHANGE_TAG,
+ LogLevel.DEBUG,
+ { str1 = tiles.toString() },
+ { "Tiles reordered: $str1" }
+ )
+ }
+
+ private companion object {
+ const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
index bf22563..4cdabae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.qs.pipeline.shared.TileSpec
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
+interface GridTypeConsistencyInteractor {
+ /**
+ * Given a list of tiles, return the best list of the same tiles (preserving as much order as
+ * possible, such that it's consistent with the current layout.
+ */
+ fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec>
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index 1aec193..ccc1c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,10 @@
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
@SysUISingleton
class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
- val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs
+ val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
new file mode 100644
index 0000000..74e906c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class InfiniteGridConsistencyInteractor
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridTypeConsistencyInteractor {
+
+ /**
+ * Tries to fill in every columns of all rows (except the last row), potentially reordering
+ * tiles.
+ */
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
+ val newTiles: MutableList<TileSpec> = mutableListOf()
+ val row = TileRow(columns = gridSizeInteractor.columns.value)
+ val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
+ val tilesQueue =
+ ArrayDeque(
+ tiles.map {
+ SizedTile(
+ it,
+ width =
+ if (iconTilesSet.contains(it)) {
+ 1
+ } else {
+ 2
+ }
+ )
+ }
+ )
+
+ while (tilesQueue.isNotEmpty()) {
+ if (row.isFull()) {
+ newTiles.addAll(row.tileSpecs())
+ row.clear()
+ }
+
+ val tile = tilesQueue.removeFirst()
+
+ // If the tile fits in the row, add it.
+ if (!row.maybeAddTile(tile)) {
+ // If the tile does not fit the row, find an icon tile to move.
+ // We'll try to either add an icon tile from the queue to complete the row, or
+ // remove an icon tile from the current row to free up space.
+
+ val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+ if (iconTile != null) {
+ tilesQueue.remove(iconTile)
+ tilesQueue.addFirst(tile)
+ row.maybeAddTile(iconTile)
+ } else {
+ val tileToRemove: SizedTile? = row.findLastIconTile()
+ if (tileToRemove != null) {
+ row.removeTile(tileToRemove)
+ row.maybeAddTile(tile)
+
+ // Moving the icon tile to the end because there's no other
+ // icon tiles in the queue.
+ tilesQueue.addLast(tileToRemove)
+ } else {
+ // If the row does not have an icon tile, add the incomplete row.
+ // Note: this shouldn't happen because an icon tile is guaranteed to be in a
+ // row that doesn't have enough space for a large tile.
+ val tileSpecs = row.tileSpecs()
+ Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
+ newTiles.addAll(tileSpecs)
+ row.clear()
+ tilesQueue.addFirst(tile)
+ }
+ }
+ }
+ }
+
+ // Add last row that might be incomplete
+ newTiles.addAll(row.tileSpecs())
+
+ return newTiles.toList()
+ }
+
+ /** Tile with a width representing the number of columns it should take. */
+ private data class SizedTile(val spec: TileSpec, val width: Int)
+
+ private class TileRow(private val columns: Int) {
+ private var availableColumns = columns
+ private val tiles: MutableList<SizedTile> = mutableListOf()
+
+ fun tileSpecs(): List<TileSpec> {
+ return tiles.map { it.spec }
+ }
+
+ fun maybeAddTile(tile: SizedTile): Boolean {
+ if (availableColumns - tile.width >= 0) {
+ tiles.add(tile)
+ availableColumns -= tile.width
+ return true
+ }
+ return false
+ }
+
+ fun findLastIconTile(): SizedTile? {
+ return tiles.findLast { it.width == 1 }
+ }
+
+ fun removeTile(tile: SizedTile) {
+ tiles.remove(tile)
+ availableColumns += tile.width
+ }
+
+ fun clear() {
+ tiles.clear()
+ availableColumns = columns
+ }
+
+ fun isFull(): Boolean = availableColumns == 0
+ }
+
+ private companion object {
+ const val TAG = "InfiniteGridConsistencyInteractor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
index bf22563..13c6072 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
+@SysUISingleton
+class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) {
+ val columns: StateFlow<Int> = repo.columns
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
new file mode 100644
index 0000000..0386a6a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */
+@SysUISingleton
+class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
index bf22563..884cde3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.shared.model
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import javax.inject.Qualifier
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class GridConsistencyLog()
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 6539cf3..dc43091 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.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
@@ -64,20 +66,20 @@
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.integerResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
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
import com.android.systemui.common.ui.compose.load
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
@@ -95,8 +97,12 @@
import kotlinx.coroutines.flow.mapLatest
@SysUISingleton
-class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) :
- GridLayout {
+class InfiniteGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor
+) : GridLayout {
private object TileType
@@ -110,10 +116,10 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by
- iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState()
+ val columns by gridSizeInteractor.columns.collectAsState()
- TileLazyGrid(modifier) {
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
items(
tiles.size,
span = { index ->
@@ -134,7 +140,7 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class)
+ @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
@Composable
private fun Tile(
tile: TileViewModel,
@@ -147,28 +153,39 @@
.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
+ )
+ }
}
}
@@ -187,8 +204,9 @@
val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet())
val isIconOnly: (TileSpec) -> Boolean =
remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+ val columns by gridSizeInteractor.columns.collectAsState()
- TileLazyGrid(modifier = modifier) {
+ TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
// These Text are just placeholders to see the different sections. Not final UI.
item(span = { GridItemSpan(maxLineSpan) }) {
Text("Current tiles", color = Color.White)
@@ -372,11 +390,11 @@
@Composable
private fun TileLazyGrid(
modifier: Modifier = Modifier,
+ columns: GridCells,
content: LazyGridScope.() -> Unit,
) {
LazyVerticalGrid(
- columns =
- GridCells.Fixed(integerResource(R.integer.quick_settings_infinite_grid_num_columns)),
+ columns = columns,
verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
modifier = modifier,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index af1d195..c8fbeb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -18,6 +18,8 @@
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.flags.NewQsUI
+import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor
import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor
import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
@@ -34,6 +36,7 @@
private val autoAddInteractor: AutoAddInteractor,
private val featureFlags: QSPipelineFlagsRepository,
private val restoreReconciliationInteractor: RestoreReconciliationInteractor,
+ private val gridConsistencyInteractor: GridConsistencyInteractor,
) : CoreStartable {
override fun start() {
@@ -41,6 +44,10 @@
accessibilityTilesInteractor.init(currentTilesInteractor)
autoAddInteractor.init(currentTilesInteractor)
restoreReconciliationInteractor.start()
+
+ if (NewQsUI.isEnabled) {
+ gridConsistencyInteractor.start()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index ffa3b54..4c21080 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -25,10 +25,13 @@
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.impl.di.QSTileComponent
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -47,7 +50,7 @@
* binding them together. This achieves a DI scope that lives along the instance of
* [QSTileViewModelImpl].
*/
- class Component<T>
+ class Component
@Inject
constructor(
private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
@@ -58,7 +61,8 @@
private val qsTileConfigProvider: QSTileConfigProvider,
private val systemClock: SystemClock,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- ) : QSTileViewModelFactory<T> {
+ private val customTileComponentBuilder: CustomTileComponent.Builder,
+ ) : QSTileViewModelFactory<CustomTileDataModel> {
/**
* Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
@@ -66,10 +70,10 @@
*/
fun create(
tileSpec: TileSpec,
- componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
- ): QSTileViewModelImpl<T> {
+ ): QSTileViewModel {
val config = qsTileConfigProvider.getConfig(tileSpec.spec)
- val component = componentFactory(config)
+ val component =
+ customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
return QSTileViewModelImpl(
config,
component::userActionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 5122e1f..f65fdb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -22,9 +22,6 @@
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
-import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
-import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -41,8 +38,7 @@
private val adapterFactory: QSTileViewModelAdapter.Factory,
private val tileMap:
Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
- private val customTileComponentBuilder: CustomTileComponent.Builder,
- private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
+ private val customTileViewModelFactory: QSTileViewModelFactory.Component,
) : QSFactory {
init {
@@ -68,7 +64,5 @@
}
private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
- customTileViewModelFactory.create(spec) { config ->
- customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
- }
+ customTileViewModelFactory.create(spec)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 17698f9d..6cf2e52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -29,7 +29,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -63,7 +63,7 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
sceneBackInteractor: SceneBackInteractor,
- val mediaDataManager: MediaDataManager,
+ val mediaCarouselInteractor: MediaCarouselInteractor,
) {
private val backScene: StateFlow<SceneKey> =
sceneBackInteractor.backScene
@@ -101,6 +101,8 @@
),
)
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation
+
private fun destinationScenes(
isUnlocked: Boolean,
canSwipeToDismiss: Boolean?,
@@ -143,9 +145,4 @@
}
return footerActionsViewModelFactory.create(lifecycleOwner)
}
-
- fun isMediaVisible(): Boolean {
- // TODO(b/328207006): use new pipeline to handle updates while visible
- return mediaDataManager.hasAnyMediaOrRecommendation()
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index d15a488..f509ef5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -27,7 +27,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
@@ -61,7 +61,7 @@
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
- val mediaDataManager: MediaDataManager,
+ val mediaCarouselInteractor: MediaCarouselInteractor,
shadeInteractor: ShadeInteractor,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
@@ -109,6 +109,8 @@
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+ val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+
/**
* Amount of X-axis translation to apply to various elements as the unfolded foldable is folded
* slightly, in pixels.
@@ -126,11 +128,6 @@
sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked")
}
- fun isMediaVisible(): Boolean {
- // TODO(b/296122467): handle updates to carousel visibility while scene is still visible
- return mediaDataManager.hasActiveMediaOrRecommendation()
- }
-
private val footerActionsControllerInitialized = AtomicBoolean(false)
fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7983db1..2446473 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 @@
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 @@
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 @@
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 6a38f8d..d2d0aaa 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.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 @@
@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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
}
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 23c0a0d..747cb3c 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 @@
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 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 @@
}
}
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 05e8717..2af119f 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 @@
/**
* 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 @@
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 162e8af..291dc13 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 @@
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 0344b32..d4f8ea3 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 @@
/** 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 e520957..5f4e832 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 @@
}
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 @@
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 5551ab4..bd7bd59 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 @@
}
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 232b4e9..bfc7425 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.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 @@
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 @@
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 @@
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 @@
.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 @@
// 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 @@
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 d0cebae..0fcfc4b 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.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 @@
private float mSmallCornerRadius;
private float mLargeCornerRadius;
private int mHeadsUpAppearHeightBottom;
+ private int mHeadsUpCyclingPadding;
public StackScrollAlgorithm(
Context context,
@@ -99,6 +101,8 @@
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 @@
&& !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 @@
}
}
+ /**
+ * @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 @@
}
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 @@
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 @@
}
}
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 5963d35..5dc5449 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 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 @@
@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 @@
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 @@
}
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 @@
.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 @@
}
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 @@
}
};
} 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 @@
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/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
index ab6a37b..d9e19d8 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt
@@ -17,23 +17,15 @@
package com.android.systemui.util.kotlin
import android.content.SharedPreferences
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.mapNotNull
object SharedPreferencesExt {
- /**
- * Returns a flow of [Unit] that is invoked each time shared preference is updated.
- *
- * @param key Optional key to limit updates to a particular key.
- */
- fun SharedPreferences.observe(key: String? = null): Flow<Unit> =
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) }
- registerOnSharedPreferenceChangeListener(listener)
- awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
- }
- .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null }
+ /** Returns a flow of [Unit] that is invoked each time shared preference is updated. */
+ fun SharedPreferences.observe(): Flow<Unit> = conflatedCallbackFlow {
+ val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+ registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { unregisterOnSharedPreferenceChangeListener(listener) }
+ }
}
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 25e5470..3164f8e 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.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 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.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 @@
*/
@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 @@
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 @@
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 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 @@
@Test
public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException {
mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mFullscreenMagnificationController)
.onFullscreenMagnificationActivationChanged(eq(true));
@@ -141,7 +157,7 @@
public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException {
mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY,
mAnimationCallback);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).deleteWindowMagnification(
mAnimationCallback);
@@ -150,7 +166,7 @@
@Test
public void setScaleForWindowMagnification() throws RemoteException {
mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).setScale(3.0f);
}
@@ -158,7 +174,7 @@
@Test
public void moveWindowMagnifier() throws RemoteException {
mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f);
- waitForIdleSync();
+ processAllPendingMessages();
verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f);
}
@@ -167,37 +183,102 @@
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 @@
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 @@
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 @@
}
}
-
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 2172bc5..8695c01 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 2817780..c308507 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 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 @@
}
@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 687e91a..c3a806b 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 @@
}
@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 a77169e..b1a8dd1 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 @@
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 @@
.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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
@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 @@
* 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 @@
)
}
+ /** */
@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 @@
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/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..db752dd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class GridConsistencyInteractorTest : SysuiTestCase() {
+
+ data object TestGridLayoutType : GridLayoutType
+
+ private val gridLayout: MutableStateFlow<GridLayoutType> =
+ MutableStateFlow(InfiniteGridLayoutType)
+
+ private val iconOnlyTiles =
+ MutableStateFlow(
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ )
+ )
+
+ private val kosmos =
+ testKosmos().apply {
+ iconTilesRepository =
+ object : IconTilesRepository {
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ get() = iconOnlyTiles.asStateFlow()
+ }
+ gridConsistencyInteractorsMap =
+ mapOf(
+ Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
+ Pair(TestGridLayoutType, noopGridConsistencyInteractor)
+ )
+ gridLayoutTypeRepository =
+ object : GridLayoutTypeRepository {
+ override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
+ }
+ }
+
+ private val underTest = with(kosmos) { gridConsistencyInteractor }
+
+ @Before
+ fun setUp() {
+ gridLayout.value = InfiniteGridLayoutType
+ underTest.start()
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun changeLayoutType_usesCorrectGridConsistencyInteractor() =
+ with(kosmos) {
+ testScope.runTest {
+ // Using the no-op grid consistency interactor
+ gridLayout.value = TestGridLayoutType
+
+ // Setting an invalid layout with holes
+ // [ Large A ] [ sa ]
+ // [ Large B ] [ Large C ]
+ // [ sb ] [ Large D ]
+ val newTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeD"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(newTiles)
+ }
+ }
+
+ @Test
+ fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Setting a valid layout with holes
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val newTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(newTiles)
+ }
+ }
+
+ @Test
+ fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Setting an invalid layout with holes
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ sb ] [ sc ]
+ // [ sd ] [ se ] [ Large C ]
+ val newTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+ tileSpecRepository.setTiles(0, newTiles)
+
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+ val tileSpecs = tiles.map { it.spec }
+
+ // Expected grid
+ // [ sa ] [ Large A ] [ sb ]
+ // [ Large B ] [ sc ] [ sd ]
+ // [ se ] [ Large C ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+
+ // Saved tiles should be unchanged
+ assertThat(tileSpecs).isEqualTo(expectedTiles)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
new file mode 100644
index 0000000..bda48ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.panels.data.repository.IconTilesRepository
+import com.android.systemui.qs.panels.data.repository.iconTilesRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
+
+ private val iconOnlyTiles =
+ MutableStateFlow(
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ )
+ )
+ private val kosmos =
+ testKosmos().apply {
+ iconTilesRepository =
+ object : IconTilesRepository {
+ override val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ get() = iconOnlyTiles.asStateFlow()
+ }
+ }
+ private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
+
+ @Test
+ fun validTiles_returnsUnchangedList() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(tiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_moveIconTileForward() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ Large A ] [ sa ]
+ // [ Large B ] [ Large C ]
+ // [ sb ] [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeD"),
+ )
+ // Expected grid
+ // [ Large A ] [ sa ][ sb ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_moveIconTileBack() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ Large C ]
+ // [ Large D ]
+ val tiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ )
+ // Expected grid
+ // [ Large A ] [ Large B ]
+ // [ Large C ] [ Large D ]
+ // [ sa ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("largeC"),
+ TileSpec.create("largeD"),
+ TileSpec.create("smallA"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+
+ @Test
+ fun invalidTiles_multipleCorrections() =
+ with(kosmos) {
+ testScope.runTest {
+ // Original grid
+ // [ sa ] [ Large A ]
+ // [ Large B ] [ sb ] [ sc ]
+ // [ sd ] [ se ] [ Large C ]
+ val tiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+ // Expected grid
+ // [ sa ] [ Large A ] [ sb ]
+ // [ Large B ] [ sc ] [ sd ]
+ // [ se ] [ Large C ]
+ val expectedTiles =
+ listOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("largeA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("largeB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
+ TileSpec.create("largeC"),
+ )
+
+ val newTiles = underTest.reconcileTiles(tiles)
+
+ assertThat(newTiles).isEqualTo(expectedTiles)
+ }
+ }
+}
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 4f0f91a..926c35f 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 @@
/* 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 aac3640..56e5e29 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.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 b38acc8..29167d6 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 @@
fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor,
notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor,
- sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 3f91122..d72630d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -32,6 +32,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.foregroundServicesRepository
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -48,7 +49,7 @@
QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake)
}
-var Kosmos.qsTileFactory by Fixture<QSFactory>()
+var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) }
val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() }
@@ -98,3 +99,7 @@
showPowerButton = true,
)
}
+
+private fun tileCreator(spec: String): QSTile {
+ return FakeQSTile(0).apply { tileSpec = spec }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index f846d57..4acedaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,4 +18,5 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
+var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
+ Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
index 685e772..e40152a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt
@@ -18,4 +18,4 @@
import com.android.systemui.kosmos.Kosmos
-val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
+var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
index bf22563..d8af3fa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.data.repository
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
new file mode 100644
index 0000000..edbc4c1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.gridConsistencyInteractor by
+ Kosmos.Fixture {
+ GridConsistencyInteractor(
+ gridLayoutTypeInteractor,
+ currentTilesInteractor,
+ gridConsistencyInteractorsMap,
+ noopGridConsistencyInteractor,
+ FakeLogBuffer.Factory.create(),
+ applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c951642..34e99d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -27,3 +27,6 @@
val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+
+var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by
+ Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
index bf22563..7f387d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridConsistencyInteractor by
+ Kosmos.Fixture {
+ InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 1893c30..34b266a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -19,4 +19,5 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
-val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) }
+val Kosmos.infiniteGridLayout by
+ Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
index bf22563..6e11977 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.infiniteGridSizeInteractor by
+ Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
index bf22563..e3beff7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.ui.viewmodel
+package com.android.systemui.qs.panels.domain.interactor
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import javax.inject.Inject
+import com.android.systemui.kosmos.Kosmos
-class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) {
- val isMediaVisible: Boolean
- get() = mediaDataManager.hasActiveMediaOrRecommendation()
-}
+val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 5fd8762..9481fca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,8 +20,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
-import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
val Kosmos.tileGridViewModel by
@@ -30,7 +29,7 @@
gridLayoutTypeInteractor,
gridLayoutMap,
currentTilesInteractor,
- InfiniteGridLayout(iconTilesInteractor),
+ infiniteGridLayout,
applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index 5c4b390..419e781 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -16,23 +16,63 @@
package com.android.systemui.qs.tiles.di
+import android.os.UserHandle
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.instanceIdSequenceFake
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import javax.inject.Provider
-import org.mockito.Mockito
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() }
+val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by
+ Kosmos.Fixture {
+ mock {
+ whenever(create(any())).thenAnswer { invocation ->
+ val tileSpec = invocation.getArgument<TileSpec>(0)
+ val config =
+ QSTileConfig(
+ tileSpec,
+ QSTileUIConfig.Empty,
+ instanceIdSequenceFake.newInstanceId(),
+ )
+ object : QSTileViewModel {
+ override val state: SharedFlow<QSTileState> =
+ MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+ override val config: QSTileConfig = config
+ override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+
+ override fun onUserChanged(user: UserHandle) {}
+
+ override fun forceUpdate() {}
+
+ override fun onActionPerformed(userAction: QSTileUserAction) {}
+
+ override fun destroy() {}
+ }
+ }
+ }
+ }
+
val Kosmos.newQSTileFactory by
Kosmos.Fixture {
NewQSTileFactory(
qSTileConfigProvider,
qsTileViewModelAdaperFactory,
newFactoryTileMap,
- mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
- mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)),
+ customTileViewModelFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 561e254..7b9992d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -38,10 +38,10 @@
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
-var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
var Kosmos.customTileQsTileConfig: QSTileConfig by
- Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+ Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } }
val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
@@ -50,7 +50,7 @@
val Kosmos.customTileInteractor: CustomTileInteractor by
Kosmos.Fixture {
CustomTileInteractor(
- tileSpec,
+ customTileSpec,
customTileDefaultsRepository,
customTileRepository,
testScope.backgroundScope,
@@ -61,7 +61,7 @@
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
- tileSpec,
+ customTileSpec,
customTileStatePersister,
packageManagerAdapterFacade,
testScope.testScheduler,
@@ -75,12 +75,12 @@
Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
- Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+ Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) }
val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
Kosmos.Fixture {
CustomTileServiceInteractor(
- tileSpec,
+ customTileSpec,
activityStarter,
{ customTileUserActionInteractor },
customTileInteractor,
@@ -95,7 +95,7 @@
Kosmos.Fixture {
CustomTileUserActionInteractor(
testCase.context,
- tileSpec,
+ customTileSpec,
qsTileLogger,
mock {},
mock {},
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 249b3cb..06a0297 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5731,7 +5731,9 @@
// so we tear it down in anticipation of it (possibly) being
// reconstructed due to the restore
host.widgets.remove(widget);
- provider.widgets.remove(widget);
+ if (provider != null) {
+ provider.widgets.remove(widget);
+ }
// Check if we need to destroy any services (if no other app widgets are
// referencing the same service)
decrementAppWidgetServiceRefCount(widget);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 763879e..6fc05b7 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 @@
}
@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 92acce2..588266f 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 @@
@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 @@
}
@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 9c84b12..f289115 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.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 @@
/**
* 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 @@
});
}
+ /** 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 @@
+ " 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 @@
event.mIsCredentialRequest,
event.mWebviewRequestedCredential,
event.mViewFillableTotalCount,
- event.mViewFillFailureCount);
+ event.mViewFillFailureCount,
+ event.mFocusedId,
+ event.mViewFillSuccessCount,
+ event.mViewFilledButUnexpectedCount);
mEventInternal = Optional.empty();
}
@@ -689,7 +732,14 @@
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 8b13c4b7..07b16c5 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 @@
mFieldClassificationIdSnapshot);
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
+ mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
}
@GuardedBy("mLock")
@@ -5381,7 +5382,20 @@
}
}
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 @@
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 70443f9..38a412f 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 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 d7c65c7..70af49c 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 @@
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 @@
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 @@
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 748253f..1a8c3b0 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.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 @@
}
};
+ 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 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 @@
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 @@
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 bef5c612..94bf813 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.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 @@
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 @@
+ " 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 @@
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 @@
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 9e06b75..26aa053 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 @@
/**
* 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 @@
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 @@
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 @@
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 00c2df6..46ed1fd 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 @@
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 @@
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 cc6ae5c..8647750 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 @@
}
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,74 +3291,66 @@
+ " 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
- }
+ 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) {
- 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);
- }
+ }
+ } 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);
- }
+ 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);
- }
+ }
+ } catch (Exception e) {
+ if (DEBUG_ALL) {
+ Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e);
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 85eb044..1db3483 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 @@
+ " 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 @@
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 2285826..2760ccf 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.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 @@
*
* @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 @@
* @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 @@
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 @@
* 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 @@
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 @@
/**
* 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 @@
// 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 @@
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 @@
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 @@
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 @@
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index add8491..c310822 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9778,9 +9778,9 @@
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.MASTER_MONO), false, this);
+ Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.MASTER_BALANCE), false, this);
+ Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL);
mEncodedSurroundMode = mSettings.getGlobalInt(
mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT,
diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java
index 3e8acee..7cf2d30 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 @@
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 @@
}
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 645a366..390ee96 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.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 @@
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 @@
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 @@
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 @@
+ ", 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 @@
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 22f3bbd..fa8299b 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 @@
*/
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 @@
/**
* 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 @@
* @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 @@
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 bcdb442..a29e852 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 @@
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 @@
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 @@
/**
* 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 @@
* 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 2703a2c0..7e18d84 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 @@
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 308aed6..8317991 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 @@
// 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 @@
}
@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 @@
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 @@
Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputPort);
+ mUniqueIdAssociationsByPort.remove(inputPort);
}
mNative.changeUniqueIdAssociation();
}
@@ -2101,9 +2102,9 @@
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 @@
// 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 6eae9a4..d7d57df 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 @@
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 035a748..00bc751 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 @@
@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 @@
}
@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 @@
mPendingInlineSuggestionsRequest.mCallback,
mPendingInlineSuggestionsRequest.mPackageName,
mService.getCurTokenDisplayIdLocked(),
- mService.getCurTokenLocked(),
- mService);
+ mService.getCurTokenLocked());
curMethod.onCreateInlineSuggestionsRequest(
mPendingInlineSuggestionsRequest.mRequestInfo, callback);
} else {
@@ -161,22 +178,20 @@
* 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 @@
+ "].");
}
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 1d048cb..e8543f2 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 @@
*
* @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 @@
@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 25e2e3a..848f74e 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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
@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 @@
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 @@
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 @@
(startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
}
- InputBindResult bindResult = tryReuseConnectionLocked(cs);
+ InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
if (bindResult != null) {
return bindResult;
}
@@ -2383,8 +2331,9 @@
@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 @@
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 @@
mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
mCurTokenDisplayId = INVALID_DISPLAY;
- mCurHostInputToken = null;
+ mAutofillController.onResetSystemUi();
}
@GuardedBy("ImfLock.class")
@@ -3774,6 +3724,7 @@
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 @@
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 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
+ imeDispatcher, userData);
didStart = true;
}
break;
@@ -3831,7 +3782,6 @@
// 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 @@
res = startInputUncheckedLocked(cs, inputContext,
remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
- imeDispatcher);
+ imeDispatcher, userData);
} else {
res = InputBindResult.NULL_EDITOR_INFO;
}
@@ -4476,6 +4426,7 @@
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 @@
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 @@
@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 = mCurHostInputToken;
+ curHostInputToken = mAutofillController.getCurHostInputToken();
+ if (curHostInputToken == null) {
+ return false;
+ }
}
return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
}
@@ -5942,14 +5896,27 @@
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 42ec1c3..61054a9 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 @@
// 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/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 93f26ae..c85ceac 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -642,6 +642,8 @@
.getPackages()
.get(0)
.getVersionRolledBackFrom();
+ Slog.i(TAG, "Rolling back high impact rollback for package: "
+ + firstRollback.getPackageName());
rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 519c0ed..7fc0292 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -293,6 +293,8 @@
return "REASON_APP_NOT_RESPONDING";
case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
return "REASON_NATIVE_CRASH_DURING_BOOT";
+ case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
+ return "REASON_BOOT_LOOP";
default:
return "UNKNOWN";
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 2bf0b2c..55f85ea2 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -22,6 +22,8 @@
import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE;
import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -31,12 +33,20 @@
import android.security.attestationverification.IAttestationVerificationManagerService;
import android.security.attestationverification.IVerificationResult;
import android.security.attestationverification.VerificationToken;
+import android.text.TextUtils;
import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
+import android.util.TimeUtils;
import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayDeque;
+
/**
* A {@link SystemService} which provides functionality related to verifying attestations of
* (usually) remote computing environments.
@@ -46,11 +56,13 @@
public class AttestationVerificationManagerService extends SystemService {
private static final String TAG = "AVF";
+ private static final int DUMP_EVENT_LOG_SIZE = 10;
private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier;
+ private final DumpLogger mDumpLogger = new DumpLogger();
public AttestationVerificationManagerService(final Context context) throws Exception {
super(context);
- mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context);
+ mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger);
}
private final IBinder mService = new IAttestationVerificationManagerService.Stub() {
@@ -83,6 +95,28 @@
private void enforceUsePermission() {
getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null);
}
+
+ @Override
+ protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
+ @Nullable String[] args) {
+ if (!android.security.Flags.dumpAttestationVerifications()) {
+ super.dump(fd, writer, args);
+ return;
+ }
+
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return;
+
+ final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " ");
+
+ fout.print("AttestationVerificationManagerService");
+ fout.println();
+ fout.increaseIndent();
+
+ fout.println("Event Log:");
+ fout.increaseIndent();
+ mDumpLogger.dumpTo(fout);
+ fout.decreaseIndent();
+ }
};
private void verifyAttestationForAllVerifiers(
@@ -119,4 +153,45 @@
Slog.d(TAG, "Started");
publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService);
}
+
+
+ static class DumpLogger {
+ private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE);
+ private int mEventsLogged = 0;
+
+ void logAttempt(DumpData data) {
+ synchronized (mData) {
+ if (mData.size() == DUMP_EVENT_LOG_SIZE) {
+ mData.removeFirst();
+ }
+
+ mEventsLogged++;
+ data.mEventNumber = mEventsLogged;
+
+ data.mEventTimeMs = System.currentTimeMillis();
+
+ mData.add(data);
+ }
+ }
+
+ void dumpTo(IndentingPrintWriter writer) {
+ synchronized (mData) {
+ for (DumpData data : mData.reversed()) {
+ writer.println(
+ TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber,
+ TimeUtils.formatForLogging(data.mEventTimeMs)));
+ writer.increaseIndent();
+ data.dumpTo(writer);
+ writer.decreaseIndent();
+ }
+ }
+ }
+ }
+
+ abstract static class DumpData {
+ protected int mEventNumber = -1;
+ protected long mEventTimeMs = -1;
+
+ abstract void dumpTo(IndentingPrintWriter writer);
+ }
}
diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
index 72a402d..945a340 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java
@@ -30,15 +30,19 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
+import android.security.attestationverification.AttestationVerificationManager;
import android.security.attestationverification.AttestationVerificationManager.LocalBindingType;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger;
import org.json.JSONObject;
@@ -71,7 +75,9 @@
/**
* Verifies Android key attestation according to the
- * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE}
+ * {@link
+ * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE
+ * PROFILE_PEER_DEVICE}
* profile.
*
* <p>
@@ -118,9 +124,12 @@
private final LocalDate mTestLocalPatchDate;
private final CertificateFactory mCertificateFactory;
private final CertPathValidator mCertPathValidator;
+ private final DumpLogger mDumpLogger;
- AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception {
+ AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
+ @NonNull DumpLogger dumpLogger) throws Exception {
mContext = Objects.requireNonNull(context);
+ mDumpLogger = dumpLogger;
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = getTrustAnchors();
@@ -132,9 +141,10 @@
// Use ONLY for hermetic unit testing.
@VisibleForTesting
AttestationVerificationPeerDeviceVerifier(@NonNull Context context,
- Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
+ DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled,
LocalDate systemDate, LocalDate localPatchDate) throws Exception {
mContext = Objects.requireNonNull(context);
+ mDumpLogger = dumpLogger;
mCertificateFactory = CertificateFactory.getInstance("X.509");
mCertPathValidator = CertPathValidator.getInstance("PKIX");
mTrustAnchors = trustAnchors;
@@ -153,63 +163,90 @@
* bounded at the end by {@code -----END CERTIFICATE-----}.
*
* @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported.
- * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
- * @param attestation Certificates should be DER encoded with leaf certificate appended first.
+ * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported.
+ * @param attestation Certificates should be DER encoded with leaf certificate appended
+ * first.
*/
int verifyAttestation(
@LocalBindingType int localBindingType,
@NonNull Bundle requirements,
@NonNull byte[] attestation) {
+
+ MyDumpData dumpData = new MyDumpData();
+
+ int result =
+ verifyAttestationInternal(localBindingType, requirements, attestation, dumpData);
+ dumpData.mResult = result;
+ mDumpLogger.logAttempt(dumpData);
+ return result;
+ }
+
+ private int verifyAttestationInternal(
+ @LocalBindingType int localBindingType,
+ @NonNull Bundle requirements,
+ @NonNull byte[] attestation,
+ @NonNull MyDumpData dumpData) {
if (mCertificateFactory == null) {
debugVerboseLog("Unable to access CertificateFactory");
return RESULT_FAILURE;
}
+ dumpData.mCertificationFactoryAvailable = true;
if (mCertPathValidator == null) {
debugVerboseLog("Unable to access CertPathValidator");
return RESULT_FAILURE;
}
+ dumpData.mCertPathValidatorAvailable = true;
+
// Check if the provided local binding type is supported and if the provided requirements
// "match" the binding type.
if (!validateAttestationParameters(localBindingType, requirements)) {
return RESULT_FAILURE;
}
+ dumpData.mAttestationParametersOk = true;
+
+ // To provide the most information in the dump logs, we track the failure state but keep
+ // verifying the rest of the attestation. For code safety, there are no transitions past
+ // here to set failed = false
+ boolean failed = false;
try {
// First: parse and validate the certificate chain.
final List<X509Certificate> certificateChain = getCertificates(attestation);
// (returns void, but throws CertificateException and other similar Exceptions)
validateCertificateChain(certificateChain);
+ dumpData.mCertChainOk = true;
final var leafCertificate = certificateChain.get(0);
final var attestationExtension = fromCertificate(leafCertificate);
// Second: verify if the attestation satisfies the "peer device" profile.
- if (!checkAttestationForPeerDeviceProfile(attestationExtension)) {
- return RESULT_FAILURE;
+ if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) {
+ failed = true;
}
// Third: check if the attestation satisfies local binding requirements.
if (!checkLocalBindingRequirements(
- leafCertificate, attestationExtension, localBindingType, requirements)) {
- return RESULT_FAILURE;
+ leafCertificate, attestationExtension, localBindingType, requirements,
+ dumpData)) {
+ failed = true;
}
-
- return RESULT_SUCCESS;
} catch (CertificateException | CertPathValidatorException
- | InvalidAlgorithmParameterException | IOException e) {
+ | InvalidAlgorithmParameterException | IOException e) {
// Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates()
// or validateCertificateChain() or
// AndroidKeystoreAttestationVerificationAttributes.fromCertificate())
debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e);
- return RESULT_FAILURE;
+ failed = true;
} catch (RuntimeException e) {
// Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions
// out of this class/method.
debugVerboseLog("Unexpected error", e);
- return RESULT_FAILURE;
+ failed = true;
}
+
+ return failed ? RESULT_FAILURE : RESULT_SUCCESS;
}
@NonNull
@@ -255,7 +292,7 @@
private void validateCertificateChain(List<X509Certificate> certificates)
throws CertificateException, CertPathValidatorException,
- InvalidAlgorithmParameterException {
+ InvalidAlgorithmParameterException {
if (certificates.size() < 2) {
debugVerboseLog("Certificate chain less than 2 in size.");
throw new CertificateException("Certificate chain less than 2 in size.");
@@ -277,7 +314,7 @@
private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException {
Set<TrustAnchor> modifiableSet = new HashSet<>();
try {
- for (String certString: getTrustAnchorResources()) {
+ for (String certString : getTrustAnchorResources()) {
modifiableSet.add(
new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate(
new ByteArrayInputStream(getCertificateBytes(certString))), null));
@@ -307,8 +344,9 @@
@NonNull X509Certificate leafCertificate,
@NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
@LocalBindingType int localBindingType,
- @NonNull Bundle requirements) {
+ @NonNull Bundle requirements, MyDumpData dumpData) {
// First: check non-optional (for the given local binding type) requirements.
+ dumpData.mBindingType = localBindingType;
switch (localBindingType) {
case TYPE_PUBLIC_KEY:
// Verify leaf public key matches provided public key.
@@ -336,9 +374,11 @@
throw new IllegalArgumentException("Unsupported local binding type "
+ localBindingTypeToString(localBindingType));
}
+ dumpData.mBindingOk = true;
// Second: check specified optional requirements.
if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) {
+ dumpData.mSystemOwnershipChecked = true;
if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) {
// Verify key is owned by the system.
final boolean ownedBySystem = checkOwnedBySystem(
@@ -347,6 +387,7 @@
debugVerboseLog("Certificate public key is not owned by the AndroidSystem.");
return false;
}
+ dumpData.mSystemOwned = true;
} else {
throw new IllegalArgumentException("The value of the requirement key "
+ PARAM_OWNED_BY_SYSTEM
@@ -359,73 +400,98 @@
}
private boolean checkAttestationForPeerDeviceProfile(
- @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) {
+ @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes,
+ MyDumpData dumpData) {
+ boolean result = true;
+
// Checks for support of Keymaster 4.
if (attestationAttributes.getAttestationVersion() < 3) {
debugVerboseLog("Attestation version is not at least 3 (Keymaster 4).");
- return false;
+ result = false;
+ } else {
+ dumpData.mAttestationVersionAtLeast3 = true;
}
// Checks for support of Keymaster 4.
if (attestationAttributes.getKeymasterVersion() < 4) {
debugVerboseLog("Keymaster version is not at least 4.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeymasterVersionAtLeast4 = true;
}
// First two characters are Android OS version.
if (attestationAttributes.getKeyOsVersion() < 100000) {
debugVerboseLog("Android OS version is not 10+.");
- return false;
+ result = false;
+ } else {
+ dumpData.mOsVersionAtLeast10 = true;
}
if (!attestationAttributes.isAttestationHardwareBacked()) {
debugVerboseLog("Key is not HW backed.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyHwBacked = true;
}
if (!attestationAttributes.isKeymasterHardwareBacked()) {
debugVerboseLog("Keymaster is not HW backed.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeymasterHwBacked = true;
}
if (attestationAttributes.getVerifiedBootState() != VERIFIED) {
debugVerboseLog("Boot state not Verified.");
- return false;
+ result = false;
+ } else {
+ dumpData.mBootStateIsVerified = true;
}
try {
if (!attestationAttributes.isVerifiedBootLocked()) {
debugVerboseLog("Verified boot state is not locked.");
- return false;
+ result = false;
+ } else {
+ dumpData.mVerifiedBootStateLocked = true;
}
} catch (IllegalStateException e) {
debugVerboseLog("VerifiedBootLocked is not set.", e);
- return false;
+ result = false;
}
// Patch level integer YYYYMM is expected to be within 1 year of today.
if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) {
debugVerboseLog("OS patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mOsPatchLevelInRange = true;
}
// Patch level integer YYYYMMDD is expected to be within 1 year of today.
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
debugVerboseLog("Boot patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyBootPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) {
debugVerboseLog("Vendor patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyVendorPatchLevelInRange = true;
}
if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) {
debugVerboseLog("Boot patch level is not within valid range.");
- return false;
+ result = false;
+ } else {
+ dumpData.mKeyBootPatchLevelInRange = true;
}
- return true;
+ return result;
}
private boolean checkPublicKey(
@@ -609,4 +675,99 @@
Slog.v(TAG, str);
}
}
+
+ /* Mutable data class for tracking dump data from verifications. */
+ private static class MyDumpData extends AttestationVerificationManagerService.DumpData {
+
+ // Top-Level Result
+ int mResult = -1;
+
+ // Configuration/Setup preconditions
+ boolean mCertificationFactoryAvailable = false;
+ boolean mCertPathValidatorAvailable = false;
+
+ // AttestationParameters (Valid Input Only)
+ boolean mAttestationParametersOk = false;
+
+ // Certificate Chain (Structure & Chaining Conditions)
+ boolean mCertChainOk = false;
+
+ // Binding
+ boolean mBindingOk = false;
+ int mBindingType = -1;
+
+ // System Ownership
+ boolean mSystemOwnershipChecked = false;
+ boolean mSystemOwned = false;
+
+ // Android Keystore attestation properties
+ boolean mOsVersionAtLeast10 = false;
+ boolean mKeyHwBacked = false;
+ boolean mAttestationVersionAtLeast3 = false;
+ boolean mKeymasterVersionAtLeast4 = false;
+ boolean mKeymasterHwBacked = false;
+ boolean mBootStateIsVerified = false;
+ boolean mVerifiedBootStateLocked = false;
+ boolean mOsPatchLevelInRange = false;
+ boolean mKeyBootPatchLevelInRange = false;
+ boolean mKeyVendorPatchLevelInRange = false;
+
+ @SuppressLint("WrongConstant")
+ @Override
+ public void dumpTo(IndentingPrintWriter writer) {
+ writer.println(
+ "Result: " + AttestationVerificationManager.verificationResultCodeToString(
+ mResult));
+ if (!mCertificationFactoryAvailable) {
+ writer.println("Certificate Factory Unavailable");
+ return;
+ }
+ if (!mCertPathValidatorAvailable) {
+ writer.println("Cert Path Validator Unavailable");
+ return;
+ }
+ if (!mAttestationParametersOk) {
+ writer.println("Attestation parameters set incorrectly.");
+ return;
+ }
+
+ writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail(
+ mCertChainOk));
+ if (!mCertChainOk) {
+ return;
+ }
+
+ // Binding
+ writer.println("Local Binding: " + booleanToOkFail(mBindingOk));
+ writer.increaseIndent();
+ writer.println("Binding Type: " + mBindingType);
+ writer.decreaseIndent();
+
+ if (mSystemOwnershipChecked) {
+ writer.println("System Ownership: " + booleanToOkFail(mSystemOwned));
+ }
+
+ // Keystore Attestation params
+ writer.println("KeyStore Attestation Parameters");
+ writer.increaseIndent();
+ writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10));
+ writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange));
+ writer.println(
+ "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3));
+ writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4));
+ writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked));
+ writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked));
+ writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified));
+ writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked));
+ writer.println(
+ "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange));
+ writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail(
+ mKeyVendorPatchLevelInRange));
+ writer.decreaseIndent();
+ }
+
+ private String booleanToOkFail(boolean value) {
+ return value ? "OK" : "FAILURE";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e814f17..21e4c96 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 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 202e94c6..237003a 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.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.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 @@
}
}
+ /** 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 ce1a72d..b5af806 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 @@
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 @@
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 @@
// 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 @@
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 @@
* 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 @@
* 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/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index dddc7b1..8fb83fa 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1447,14 +1447,17 @@
+ " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
}
+ final boolean contentChanged = didFrameInsetsChange || configChanged
+ || dragResizingChanged || attachedFrameChanged;
+ // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized().
+ if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0
+ && mDrawHandlers.isEmpty()) {
+ mRedrawForSyncReported = true;
+ }
+
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
- if (didFrameInsetsChange
- || configChanged
- || insetsChanged
- || dragResizingChanged
- || shouldSendRedrawForSync()
- || attachedFrameChanged) {
+ if (contentChanged || insetsChanged || shouldSendRedrawForSync()) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize reasons for w=%s: %s configChanged=%b didFrameInsetsChange=%b",
this, mWindowFrames.getInsetsChangedInfo(),
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index e65b903..22c0f73 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 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 ba89fda..a01c123 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 @@
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
- jmethodID getInputUniqueIdAssociations;
+ jmethodID getInputUniqueIdAssociationsByPort;
jmethodID getInputUniqueIdAssociationsByDescriptor;
jmethodID getDeviceTypeAssociations;
jmethodID getKeyboardLayoutAssociations;
@@ -624,10 +624,9 @@
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,
@@ -2946,8 +2945,8 @@
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/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 0c83e8e..2e126f1 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 @@
}
}
- 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 38ad5b6..b86db06 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.server.infra.AbstractPerUserSystemService;
import java.util.List;
+import java.util.Set;
/**
@@ -80,9 +83,12 @@
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 b1673e2..7a026d5 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 @@
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 @@
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 @@
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 9eb7b22..375fc5a 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 @@
== 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 622e702..54d101a 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 @@
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 1666fef..54b2d4d 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 @@
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 0000000..7f2327aa
--- /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 0716a5c..3698d6f 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.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 @@
@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 b33a8aa..00c8ed1 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 @@
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 @@
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 @@
.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 5e2fe6a..2d672b8 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 @@
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 @@
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 b90fa21..79e401c 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 @@
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.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 @@
}
@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 @@
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 afa6698..d9fd312 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 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 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/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index fbbb9a2..b152c3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -819,17 +819,20 @@
assertFalse(win.getOrientationChanging());
}
- @SetupWindows(addWindows = W_ABOVE_ACTIVITY)
@Test
public void testRequestResizeForBlastSync() {
- final WindowState win = mChildAppWindowAbove;
- makeWindowVisible(win, win.getParentWindow());
+ final WindowState win = createWindow(null, TYPE_APPLICATION, "window");
+ makeWindowVisible(win);
+ makeLastConfigReportedToClient(win, true /* visible */);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
win.reportResized();
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).doesNotContain(win);
// Check that the window is in resizing if using blast sync.
+ final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class);
+ syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST;
+ win.mSyncGroup = syncGroup;
win.reportResized();
win.prepareSync();
assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
@@ -842,6 +845,20 @@
mWm.mResizingWindows.remove(win);
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).doesNotContain(win);
+
+ // Non blast sync doesn't require to force resizing, because it won't use syncSeqId.
+ // And if the window is already drawn, it can report sync finish immediately so that the
+ // sync group won't be blocked.
+ win.finishSync(mTransaction, syncGroup, false /* cancel */);
+ syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE;
+ win.mSyncGroup = syncGroup;
+ win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+ win.prepareSync();
+ assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState);
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).doesNotContain(win);
+ assertTrue(win.isSyncFinished(syncGroup));
+ assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState);
}
@Test
diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
index dfbbda6c..afb3593 100644
--- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
+++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt
@@ -9,21 +9,28 @@
import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS
import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE
import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY
+import android.util.IndentingPrintWriter
+import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.security.AttestationVerificationManagerService.DumpLogger
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.TrustAnchor
import java.security.cert.X509Certificate
import java.time.LocalDate
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
/** Test for Peer Device attestation verifier. */
@SmallTest
@@ -31,6 +38,7 @@
class AttestationVerificationPeerDeviceVerifierTest {
private val certificateFactory = CertificateFactory.getInstance("X.509")
@Mock private lateinit var context: Context
+ private val dumpLogger = DumpLogger()
private lateinit var trustAnchors: HashSet<TrustAnchor>
@Before
@@ -44,37 +52,50 @@
}
}
+ @After
+ fun dumpAndLog() {
+ val dump = dumpLogger.getDump()
+ Log.d(TAG, "$dump")
+ }
+
@Test
fun verifyAttestation_returnsSuccessTypeChallenge() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 8, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 8, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessTypePublicKey() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 8, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 8, 1)
+ )
val leafCert =
(TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0]
@@ -84,61 +105,75 @@
val result = verifier.verifyAttestation(
TYPE_PUBLIC_KEY, pkRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsSuccessOwnedBySystem() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray())
challengeRequirements.putBoolean("android.key_owned_by_system", true)
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()
+ )
+
assertThat(result).isEqualTo(RESULT_SUCCESS)
}
@Test
fun verifyAttestation_returnsFailureOwnedBySystem() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 2, 1),
- LocalDate.of(2021, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1),
+ LocalDate.of(2021, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
challengeRequirements.putBoolean("android.key_owned_by_system", true)
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@Test
fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2023, 3, 1),
- LocalDate.of(2023, 2, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1),
+ LocalDate.of(2023, 2, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@Test
fun verifyAttestation_returnsFailureTrustedAnchorEmpty() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, HashSet(), false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
@@ -151,32 +186,39 @@
}
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
fun verifyAttestation_returnsFailureChallenge() {
val verifier = AttestationVerificationPeerDeviceVerifier(
- context, trustAnchors, false, LocalDate.of(2022, 1, 1),
- LocalDate.of(2022, 1, 1))
+ context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1),
+ LocalDate.of(2022, 1, 1)
+ )
val challengeRequirements = Bundle()
challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray())
- val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements,
- TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray())
+ val result = verifier.verifyAttestation(
+ TYPE_CHALLENGE, challengeRequirements,
+ TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()
+ )
assertThat(result).isEqualTo(RESULT_FAILURE)
}
private fun String.fromPEMFileToCerts(): Collection<Certificate> {
return certificateFactory.generateCertificates(
InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets()
- .open(this))
+ .open(this)
+ )
}
private fun String.fromPEMFileToByteArray(): ByteArray {
@@ -188,6 +230,12 @@
return bos.toByteArray()
}
+ private fun DumpLogger.getDump(): String {
+ val sw = StringWriter()
+ this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " "))
+ return sw.toString()
+ }
+
class TestActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -195,6 +243,7 @@
}
companion object {
+ private const val TAG = "AVFTest"
private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem"
private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME =
"test_attestation_with_root_certs.pem"
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 709f58d..3c72498 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 @@
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 @@
val inputDevice = createInputDevice()
// Associate input device with display
- service.addUniqueIdAssociation(
+ service.addUniqueIdAssociationByPort(
inputDevice.name,
virtualDisplays[0].display.displayId.toString()
)
@@ -358,10 +357,10 @@
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 3d5a0f7..395f744 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.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 @@
* @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 @@
*
* @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;
}