Merge "Fix IndexOutOfBoundsException in AvalancheController.getDurationMs" into main
diff --git a/apct-tests/perftests/packagemanager/AndroidTest.xml b/apct-tests/perftests/packagemanager/AndroidTest.xml
index c9d45a6..db938e4 100644
--- a/apct-tests/perftests/packagemanager/AndroidTest.xml
+++ b/apct-tests/perftests/packagemanager/AndroidTest.xml
@@ -76,11 +76,6 @@
<option name="test-file-name" value="QueriesAll49.apk"/>
</target_preparer>
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="com.android.perftests.packagemanager"/>
- <option name="hidden-api-checks" value="false"/>
- </test>
-
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/local/PackageManagerPerfTests"/>
<option name="collect-on-run-ended-only" value="true"/>
diff --git a/core/api/current.txt b/core/api/current.txt
index c189a24c..948eb68 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -282,7 +282,7 @@
field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
- field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
+ field @FlaggedApi("android.companion.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 443a6c0e..1383096 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1769,14 +1769,16 @@
}
public final class InputManager {
- method 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 @FlaggedApi("com.android.input.flags.device_associations") @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 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 @FlaggedApi("com.android.input.flags.device_associations") @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 685ea63..e6265ae 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -977,8 +977,16 @@
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#addUniqueIdAssociationByPort(String, String):
+ Method 'addUniqueIdAssociationByPort' 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#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():
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index dbde7d2..6ff1bfc5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2583,6 +2583,7 @@
this.when = System.currentTimeMillis();
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -2598,6 +2599,7 @@
{
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
}
new Builder(context)
.setWhen(when)
@@ -2630,6 +2632,7 @@
this.when = when;
if (updateRankingTime()) {
creationTime = when;
+ extras.putBoolean(EXTRA_SHOW_WHEN, true);
} else {
this.creationTime = System.currentTimeMillis();
}
@@ -4382,14 +4385,16 @@
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
*
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not
- * shown anymore by default and must be opted into by using
- * {@link android.app.Notification.Builder#setShowWhen(boolean)}
- *
* @see Notification#when
*/
@NonNull
public Builder setWhen(long when) {
+ if (updateRankingTime()) {
+ // don't show a timestamp that's decades old
+ if (mN.extras.getBoolean(EXTRA_SHOW_WHEN, true) && when == 0) {
+ return this;
+ }
+ }
mN.when = when;
return this;
}
@@ -4397,8 +4402,6 @@
/**
* Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown
* in the content view.
- * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to
- * {@code false}. For earlier apps, the default is {@code true}.
*/
@NonNull
public Builder setShowWhen(boolean show) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index eb7afb8e..481ff2e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -134,6 +134,13 @@
"enable_credential_description_api";
/**
+ * @hide
+ */
+ @Hide
+ public static final String EXTRA_AUTOFILL_RESULT_RECEIVER =
+ "android.credentials.AUTOFILL_RESULT_RECEIVER";
+
+ /**
* @hide instantiated by ContextImpl.
*/
public CredentialManager(Context context, ICredentialManager service) {
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index 2229f25..a620621 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -28,12 +28,5 @@
*/
public static final String EXTRA_RESULT_RECEIVER =
"android.credentials.selection.extra.RESULT_RECEIVER";
-
- /**
- * The intent extra key for the final result receiver object
- */
- public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
- "android.credentials.selection.extra.FINAL_RESPONSE_RECEIVER";
-
private Constants() {}
}
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index 32938ff..e5c12e3 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -16,9 +16,11 @@
package android.hardware;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Range;
import java.util.ArrayList;
import java.util.List;
+
/**
* The camera action state used for passing camera usage information from
* camera service to camera service proxy .
@@ -66,6 +68,7 @@
private int mVideoStabilizationMode;
private boolean mUsedUltraWide;
private boolean mUsedZoomOverride;
+ private Range<Integer> mMostRequestedFpsRange;
private int mSessionIndex;
private CameraExtensionSessionStats mCameraExtensionSessionStats;
@@ -86,6 +89,7 @@
mVideoStabilizationMode = -1;
mUsedUltraWide = false;
mUsedZoomOverride = false;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
mSessionIndex = 0;
mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
@@ -109,6 +113,7 @@
mVideoStabilizationMode = -1;
mUsedUltraWide = false;
mUsedZoomOverride = false;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
mSessionIndex = sessionIdx;
mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
@@ -158,6 +163,8 @@
dest.writeBoolean(mUsedZoomOverride);
dest.writeInt(mSessionIndex);
mCameraExtensionSessionStats.writeToParcel(dest, 0);
+ dest.writeInt(mMostRequestedFpsRange.getLower());
+ dest.writeInt(mMostRequestedFpsRange.getUpper());
}
public void readFromParcel(Parcel in) {
@@ -188,6 +195,9 @@
mSessionIndex = in.readInt();
mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in);
+ int minFps = in.readInt();
+ int maxFps = in.readInt();
+ mMostRequestedFpsRange = new Range<Integer>(minFps, maxFps);
}
public String getCameraId() {
@@ -273,4 +283,8 @@
public CameraExtensionSessionStats getExtensionSessionStats() {
return mCameraExtensionSessionStats;
}
+
+ public Range<Integer> getMostRequestedFpsRange() {
+ return mMostRequestedFpsRange;
+ }
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 553d9f7..5a0f3db 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -164,15 +164,9 @@
void getFeature(IBinder token, int userId, int feature, IFaceServiceReceiver receiver,
String opPackageName);
- // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
- // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
- // hidlSensors must be non-null and empty. See AuthService.java
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticators(in List<FaceSensorPropertiesInternal> hidlSensors);
-
//Register all available face sensors.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticatorsLegacy(in FaceSensorConfigurations faceSensorConfigurations);
+ void registerAuthenticators(in FaceSensorConfigurations faceSensorConfigurations);
// Adds a callback which gets called when the service registers all of the face
// authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 8b37c24..6a96ac4 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -177,13 +177,7 @@
//Register all available fingerprint sensors.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticatorsLegacy(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
-
- // Registers all HIDL and AIDL sensors. Only HIDL sensor properties need to be provided, because
- // AIDL sensor properties are retrieved directly from the available HALs. If no HIDL HALs exist,
- // hidlSensors must be non-null and empty. See AuthService.java
- @EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticators(in List<FingerprintSensorPropertiesInternal> hidlSensors);
+ void registerAuthenticators(in FingerprintSensorConfigurations fingerprintSensorConfigurations);
// Adds a callback which gets called when the service registers all of the fingerprint
// authenticators. The callback is automatically removed after it's invoked.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1c37aa2..2d474d6 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -165,10 +165,17 @@
// static association for the cleared input port will be restored.
void removePortAssociation(in String inputPort);
- // Add a runtime association between the input device and display.
- void addUniqueIdAssociation(in String inputPort, in String displayUniqueId);
- // Remove the runtime association between the input device and display.
- void removeUniqueIdAssociation(in String inputPort);
+ // Add a runtime association between the input device and display, using device's descriptor.
+ void addUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor,
+ in String displayUniqueId);
+ // Remove the runtime association between the input device and display, using device's
+ // descriptor.
+ void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor);
+
+ // Add a runtime association between the input device and display, using device's port.
+ void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId);
+ // Remove the runtime association between the input device and display, using device's port.
+ 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 f949158..4dda2c7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,6 +17,7 @@
package android.hardware.input;
import static com.android.input.flags.Flags.FLAG_INPUT_DEVICE_VIEW_BEHAVIOR_API;
+import static com.android.input.flags.Flags.FLAG_DEVICE_ASSOCIATIONS;
import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
import android.Manifest;
@@ -1054,13 +1055,14 @@
/**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
- * @param inputPort The port of the input device.
- * @param displayPort The physical port of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayPort the physical port of the associated display
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
public void addPortAssociation(@NonNull String inputPort, int displayPort) {
try {
mIm.addPortAssociation(inputPort, displayPort);
@@ -1072,12 +1074,13 @@
/**
* Remove the runtime association between the input port and the display port. Any existing
* static association for the cleared input port will be restored.
- * @param inputPort The port of the input device to be cleared.
+ * @param inputPort the port of the input device to be cleared
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
public void removePortAssociation(@NonNull String inputPort) {
try {
mIm.removePortAssociation(inputPort);
@@ -1089,30 +1092,74 @@
/**
* Add a runtime association between the input port and display, by unique id. Input ports are
* expected to be unique.
- * @param inputPort The port of the input device.
- * @param displayUniqueId The unique id of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayUniqueId the unique id of the associated display
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- public void addUniqueIdAssociation(@NonNull String inputPort,
+ public void addUniqueIdAssociationByPort(@NonNull String inputPort,
@NonNull String displayUniqueId) {
- mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId);
+ mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId);
}
/**
* Removes a runtime association between the input device and display.
- * @param inputPort The port of the input device.
+ * @param inputPort the port of the input device
* <p>
* Requires {@link android.Manifest.permission#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
* </p>
* @hide
*/
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
@TestApi
- public void removeUniqueIdAssociation(@NonNull String inputPort) {
- mGlobal.removeUniqueIdAssociation(inputPort);
+ public void removeUniqueIdAssociationByPort(@NonNull String inputPort) {
+ mGlobal.removeUniqueIdAssociationByPort(inputPort);
+ }
+
+ /**
+ * Add a runtime association between the input device name and display, by descriptor. Input
+ * device descriptors are expected to be unique per physical device, though one physical
+ * device can have multiple virtual input devices that possess the same descriptor.
+ * E.g. a keyboard with built in trackpad will be 2 different input devices with the same
+ * descriptor.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * @param displayUniqueId the unique id of the associated display
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+ * </p>
+ * @hide
+ */
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+ @TestApi
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ mGlobal.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+ }
+
+ /**
+ * Removes a runtime association between the input device and display.
+ }
+
+ /**
+ * Removes a runtime association between the input device and display.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY}.
+ * </p>
+ * @hide
+ */
+ @FlaggedApi(FLAG_DEVICE_ASSOCIATIONS)
+ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY)
+ @TestApi
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ mGlobal.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
}
/**
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 7b29666..1d253d9 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -1467,22 +1467,46 @@
}
/**
- * @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();
+ }
+ }
+
+ /**
+ * @see InputManager#addUniqueIdAssociationByDescriptor(String, String)
+ */
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ try {
+ mIm.addUniqueIdAssociationByDescriptor(inputDeviceDescriptor, displayUniqueId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @see InputManager#removeUniqueIdAssociationByDescriptor(String)
+ */
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ try {
+ mIm.removeUniqueIdAssociationByDescriptor(inputDeviceDescriptor);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 6246dd7..91cdf8d 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -124,6 +124,22 @@
"vcn_network_selection_ipsec_packet_loss_percent_threshold";
/**
+ * Key for detecting unusually large increases in IPsec packet sequence numbers.
+ *
+ * <p>If the sequence number increases by more than this value within a second, it may indicate
+ * an intentional leap on the server's downlink. To avoid false positives, the packet loss
+ * detector will suppress loss reporting.
+ *
+ * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
+ * To reduce false positives, consider setting an appropriate maximum threshold.
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
+ "vcn_network_selection_max_seq_num_increase_per_second";
+
+ /**
* Key for the list of timeouts in minute to stop penalizing an underlying network candidate
*
* @hide
@@ -180,6 +196,7 @@
VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index e64823a..6fde398 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -34,4 +34,14 @@
namespace: "vcn"
description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
bug: "323238888"
+}
+
+flag{
+ name: "handle_seq_num_leap"
+ namespace: "vcn"
+ description: "Do not report bad network when there is a suspected sequence number leap"
+ bug: "332598276"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9696dbc..9c14946 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -20,6 +20,7 @@
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.app.Notification;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -146,8 +147,13 @@
/**
* Data type: boolean, when true it suggests that the content text of this notification is
- * sensitive. A notification listener can use this information to redact notifications on locked
- * devices.
+ * sensitive. The system uses this information to improve privacy around the notification
+ * content. In {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, sensitive notification content is
+ * redacted from updates to most {@link NotificationListenerService
+ * NotificationListenerServices}. Also if an app posts a sensitive notification while
+ * {@link android.media.projection.MediaProjection screen-sharing} is active, that app's windows
+ * are blocked from screen-sharing and a {@link android.widget.Toast Toast} is shown to inform
+ * the user about this.
*/
public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 188ad8f..56edfe72 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -103,9 +103,9 @@
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
private static native void nativeDestroy(long nativeObject);
- // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
- // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
- private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+ // 5KB is a balanced guess, since these are still pretty heavyweight objects, but if we make
+ // this too big, it can overwhelm the GC.
+ private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000;
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c95d6ff..a23df79 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -951,7 +951,7 @@
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
boolean creating, boolean sizeChanged, boolean hintChanged, boolean relativeZChanged,
- Transaction surfaceUpdateTransaction) {
+ boolean hdrHeadroomChanged, Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -986,7 +986,7 @@
updateBackgroundVisibility(surfaceUpdateTransaction);
updateBackgroundColor(surfaceUpdateTransaction);
- if (mLimitedHdrEnabled) {
+ if (mLimitedHdrEnabled && hdrHeadroomChanged) {
surfaceUpdateTransaction.setDesiredHdrHeadroom(
mBlastSurfaceControl, mHdrHeadroom);
}
@@ -1203,7 +1203,7 @@
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
- creating, sizeChanged, hintChanged, relativeZChanged,
+ creating, sizeChanged, hintChanged, relativeZChanged, hdrHeadroomChanged,
surfaceUpdateTransaction);
try {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cacf0d2..ac1f646 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2428,6 +2428,26 @@
*/
public static final int FRAME_RATE_CATEGORY_REASON_IDLE = 0x0700_0000;
+ /**
+ * This indicates that the frame rate category was chosen because it is currently boosting.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_BOOST = 0x0800_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is currently having
+ * touch boost.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_TOUCH = 0x0900_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it is currently having
+ * touch boost.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_CONFLICTED = 0x0A00_0000;
+
private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
/**
@@ -5742,7 +5762,7 @@
*/
private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
- private static final float MAX_FRAME_RATE = 140;
+ static final float MAX_FRAME_RATE = 140;
private static final int INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -33897,36 +33917,41 @@
int height = mBottom - mTop;
if (viewRootImpl != null && (width != 0 && height != 0)) {
- if (mAttachInfo.mViewVelocityApi) {
- float velocity = mFrameContentVelocity;
- int mask = PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN;
- float frameRate = 0;
+ if (viewRootImpl.shouldCheckFrameRate(mPreferredFrameRate > 0f)) {
+ float velocityFrameRate = 0f;
+ if (mAttachInfo.mViewVelocityApi) {
+ float velocity = mFrameContentVelocity;
- if (velocity < 0f
- && (mPrivateFlags4 & mask) == mask
- && mParent instanceof View
- && ((View) mParent).mFrameContentVelocity <= 0
- ) {
- // This current calculation is very simple. If something on the screen moved,
- // then it votes for the highest velocity. If it doesn't move, then return 0.
- velocity = Float.POSITIVE_INFINITY;
- frameRate = MAX_FRAME_RATE;
- }
- if (velocity > 0f) {
- if (sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
- frameRate = convertVelocityToFrameRate(velocity);
+ if (velocity < 0f
+ && (mPrivateFlags4 & (PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)) == (
+ PFLAG4_HAS_MOVED | PFLAG4_HAS_DRAWN)
+ && mParent instanceof View
+ && ((View) mParent).mFrameContentVelocity <= 0
+ ) {
+ // This current calculation is very simple. If something on the screen
+ // moved, then it votes for the highest velocity.
+ velocityFrameRate = MAX_FRAME_RATE;
+ } else if (velocity > 0f) {
+ velocityFrameRate = convertVelocityToFrameRate(velocity);
}
- viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
- return;
+ }
+ if (velocityFrameRate > 0f || mPreferredFrameRate > 0f) {
+ int compatibility = FRAME_RATE_COMPATIBILITY_GTE;
+ float frameRate = velocityFrameRate;
+ if (mPreferredFrameRate > velocityFrameRate) {
+ compatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ frameRate = mPreferredFrameRate;
+ }
+ viewRootImpl.votePreferredFrameRate(frameRate, compatibility);
}
}
- if (!willNotDraw() && isDirty()) {
+ if (!willNotDraw() && isDirty() && viewRootImpl.shouldCheckFrameRateCategory()) {
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
float sizePercentage = width * height / mAttachInfo.mDisplayPixelCount;
viewRootImpl.recordViewPercentage(sizePercentage);
}
- int frameRateCategory;
+ int frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
if (Float.isNaN(mPreferredFrameRate)) {
frameRateCategory = calculateFrameRateCategory();
} else if (mPreferredFrameRate < 0) {
@@ -33951,10 +33976,6 @@
| FRAME_RATE_CATEGORY_REASON_INVALID;
}
}
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
- mFrameRateCompatibility);
- return;
}
int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cd0602c..87dfa029 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -27,6 +27,7 @@
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -34,14 +35,18 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_BOOST;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_CONFLICTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_IDLE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_TOUCH;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_UNKNOWN;
import static android.view.View.FRAME_RATE_CATEGORY_REASON_VELOCITY;
+import static android.view.View.MAX_FRAME_RATE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -1041,10 +1046,10 @@
// The preferred frame rate category of the view that
// could be updated on a frame-by-frame basis.
- private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
// The preferred frame rate category of the last frame that
// could be used to lower frame rate after touch boost
- private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
// The preferred frame rate of the view that is mainly used for
// touch boosting, view velocity handling, and TextureView.
private float mPreferredFrameRate = 0;
@@ -4190,18 +4195,25 @@
// For the variable refresh rate project.
// We set the preferred frame rate and frame rate category at the end of performTraversals
// when the values are applicable.
+ setCategoryFromCategoryCounts();
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+ if (!mIsFrameRateConflicted) {
+ mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
+ mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
+ FRAME_RATE_SETTING_REEVALUATE_TIME);
+ }
+ checkIdleness();
mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_DEFAULT;
mPreferredFrameRate = -1;
- mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
mIsFrameRateConflicted = false;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
}
private void createSyncIfNeeded() {
@@ -6628,8 +6640,6 @@
*/
mIsFrameRateBoosting = false;
mIsTouchBoosting = false;
- setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
- mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
@@ -7675,7 +7685,6 @@
mWindowAttributes.type)) {
// set the frame rate to the maximum value.
mIsTouchBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
* We want to lower the refresh rate when MotionEvent.ACTION_UP,
@@ -12467,58 +12476,50 @@
EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
}
+ /**
+ * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts.
+ */
+ private void setCategoryFromCategoryCounts() {
+ if (mFrameRateCategoryHighCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ } else if (mFrameRateCategoryHighHintCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+ } else if (mFrameRateCategoryNormalCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
+ } else if (mFrameRateCategoryLowCount > 0) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+ }
+ }
+
private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
- if (!shouldSetFrameRateCategory()
- || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && mPreferredFrameRate > 0
- && sToolkitFrameRateVelocityMappingReadOnlyFlagValue)) {
+ if (!shouldSetFrameRateCategory()) {
return;
}
- int categoryFromConflictedFrameRates = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- if (mIsFrameRateConflicted) {
- categoryFromConflictedFrameRates = mPreferredFrameRate > 60
- ? FRAME_RATE_CATEGORY_HIGH : FRAME_RATE_CATEGORY_NORMAL;
- }
- int frameRateCategory = mIsTouchBoosting
- ? FRAME_RATE_CATEGORY_HIGH_HINT
- : Math.max(preferredFrameRateCategory, categoryFromConflictedFrameRates);
+ int frameRateCategory;
+ int frameRateReason;
+ String view;
- // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
- // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
- // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
- // (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning
- || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && mPreferredFrameRate > 0)) {
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
- // We've received a velocity, so we'll let the velocity control the
- // frame rate unless we receive additional motion events.
- mIsTouchBoosting = false;
- mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
- mFrameRateCategoryView = null;
- } else {
- mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_UNKNOWN;
- }
+ frameRateReason = FRAME_RATE_CATEGORY_REASON_BOOST;
+ view = null;
+ } else if (mIsTouchBoosting && preferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH_HINT) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
+ frameRateReason = FRAME_RATE_CATEGORY_REASON_TOUCH;
+ view = null;
+ } else {
+ frameRateCategory = preferredFrameRateCategory;
+ frameRateReason = mFrameRateCategoryChangeReason;
+ view = mFrameRateCategoryView;
}
try {
- if (mLastPreferredFrameRateCategory != frameRateCategory) {
+ if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT
+ && mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- String reason = reasonToString(mFrameRateCategoryChangeReason);
- String sourceView = mFrameRateCategoryView == null ? "-"
- : mFrameRateCategoryView;
- if (preferredFrameRateCategory == FRAME_RATE_CATEGORY_HIGH_HINT) {
- reason = "touch boost";
- sourceView = "-";
- } else if (categoryFromConflictedFrameRates == frameRateCategory
- && frameRateCategory != preferredFrameRateCategory
- && mIsFrameRateConflicted
- ) {
- reason = "conflict";
- sourceView = "-";
- }
+ String reason = reasonToString(frameRateReason);
+ String sourceView = view == null ? "-" : view;
String category = categoryToString(frameRateCategory);
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
@@ -12562,24 +12563,21 @@
case FRAME_RATE_CATEGORY_REASON_VELOCITY -> str = "velocity";
case FRAME_RATE_CATEGORY_REASON_IDLE -> str = "idle";
case FRAME_RATE_CATEGORY_REASON_UNKNOWN -> str = "unknown";
+ case FRAME_RATE_CATEGORY_REASON_BOOST -> str = "boost";
+ case FRAME_RATE_CATEGORY_REASON_TOUCH -> str = "touch";
+ case FRAME_RATE_CATEGORY_REASON_CONFLICTED -> str = "conflicted";
default -> str = String.valueOf(reason);
}
return str;
}
private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
- return;
- }
- if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
- && preferredFrameRate > 0 && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
- mIsTouchBoosting = false;
+ if (!shouldSetFrameRate() || preferredFrameRate < 0) {
return;
}
try {
- if (mLastPreferredFrameRate != preferredFrameRate
- && preferredFrameRate >= 0) {
+ if (mLastPreferredFrameRate != preferredFrameRate) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate "
@@ -12588,7 +12586,7 @@
}
if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) {
mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate,
- mFrameRateCompatibility).applyAsyncUnsafe();
+ mFrameRateCompatibility).applyAsyncUnsafe();
}
mLastPreferredFrameRate = preferredFrameRate;
}
@@ -12599,12 +12597,6 @@
}
}
- private void sendDelayedEmptyMessage(int message, int delayedTime) {
- mHandler.removeMessages(message);
-
- mHandler.sendEmptyMessageDelayed(message, delayedTime);
- }
-
private boolean shouldSetFrameRateCategory() {
// use toolkitSetFrameRate flag to gate the change
return mSurface.isValid() && shouldEnableDvrr();
@@ -12642,25 +12634,34 @@
case FRAME_RATE_CATEGORY_HIGH ->
mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
}
-
- int oldCategory = mPreferredFrameRateCategory;
- if (mFrameRateCategoryHighCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
- } else if (mFrameRateCategoryHighHintCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH_HINT;
- } else if (mFrameRateCategoryNormalCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mFrameRateCategoryLowCount > 0) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_LOW;
+ if (frameRateCategory > mPreferredFrameRateCategory) {
+ mPreferredFrameRateCategory = frameRateCategory;
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view == null ? "-" : view.getClass().getSimpleName();
}
mHasInvalidation = true;
- checkIdleness();
- if (mPreferredFrameRateCategory != oldCategory
- && mPreferredFrameRateCategory == frameRateCategory
- ) {
- mFrameRateCategoryChangeReason = reason;
- mFrameRateCategoryView = view == null ? "null" : view.getClass().getSimpleName();
- }
+ }
+
+ /**
+ * Returns whether a View should vote for frame rate category. When the category is HIGH
+ * already, there's no need to calculate the category on the View and vote.
+ */
+ public boolean shouldCheckFrameRateCategory() {
+ return mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH;
+ }
+
+ /**
+ * Returns whether a View should vote for frame rate. When the maximum frame rate has already
+ * been voted for, there's no point in calculating and voting for the frame rate. When
+ * isDirect is false, then it will return false when the velocity-calculated frame rate
+ * can be avoided.
+ * @param isDirect true when the frame rate has been set directly on the View or false if
+ * the calculation is based only on velocity.
+ */
+ public boolean shouldCheckFrameRate(boolean isDirect) {
+ return mPreferredFrameRate < MAX_FRAME_RATE
+ || (!isDirect && !sToolkitFrameRateVelocityMappingReadOnlyFlagValue
+ && mPreferredFrameRateCategory < FRAME_RATE_CATEGORY_HIGH);
}
/**
@@ -12686,24 +12687,44 @@
if (frameRate <= 0) {
return;
}
+ if (frameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE) {
+ mIsTouchBoosting = false;
+ if (!sToolkitFrameRateVelocityMappingReadOnlyFlagValue) {
+ mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_VELOCITY;
+ mFrameRateCategoryView = null;
+ return;
+ }
+ }
+ float nextFrameRate;
+ int nextFrameRateCompatibility;
+ if (frameRate > mPreferredFrameRate) {
+ nextFrameRate = frameRate;
+ nextFrameRateCompatibility = frameRateCompatibility;
+ } else {
+ nextFrameRate = mPreferredFrameRate;
+ nextFrameRateCompatibility = mFrameRateCompatibility;
+ }
+
if (mPreferredFrameRate > 0 && mPreferredFrameRate % frameRate != 0
&& frameRate % mPreferredFrameRate != 0) {
mIsFrameRateConflicted = true;
- mFrameRateCompatibility = FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- }
- if (frameRate > mPreferredFrameRate) {
- mFrameRateCompatibility = frameRateCompatibility;
+ if (nextFrameRate > 60 && mFrameRateCategoryHighCount != FRAME_RATE_CATEGORY_COUNT) {
+ mFrameRateCategoryHighCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+ mFrameRateCategoryView = null;
+ } else if (mFrameRateCategoryHighCount == 0 && mFrameRateCategoryHighHintCount == 0
+ && mFrameRateCategoryNormalCount < FRAME_RATE_CATEGORY_COUNT) {
+ mFrameRateCategoryNormalCount = FRAME_RATE_CATEGORY_COUNT;
+ mFrameRateCategoryChangeReason = FRAME_RATE_CATEGORY_REASON_CONFLICTED;
+ mFrameRateCategoryView = null;
+ }
}
- mPreferredFrameRate = Math.max(mPreferredFrameRate, frameRate);
+ mPreferredFrameRate = nextFrameRate;
+ mFrameRateCompatibility = nextFrameRateCompatibility;
mHasInvalidation = true;
-
- if (!mIsFrameRateConflicted) {
- mHandler.removeMessages(MSG_FRAME_RATE_SETTING);
- mHandler.sendEmptyMessageDelayed(MSG_FRAME_RATE_SETTING,
- FRAME_RATE_SETTING_REEVALUATE_TIME);
- }
- checkIdleness();
}
/**
@@ -12773,7 +12794,6 @@
private void boostFrameRate(int boostTimeOut) {
mIsFrameRateBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT);
mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT,
boostTimeOut);
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 8f1b72e..1034479 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -40,6 +40,7 @@
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import java.io.File;
import java.lang.reflect.Method;
@@ -609,7 +610,7 @@
startedRelroProcesses = WebViewLibraryLoader.prepareNativeLibraries(packageInfo);
} catch (Throwable t) {
// Log and discard errors at this stage as we must not crash the system server.
- Log.e(LOGTAG, "error preparing webview native library", t);
+ Slog.wtf(LOGTAG, "error preparing webview native library", t);
}
WebViewZygote.onWebViewProviderChanged(packageInfo);
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index a68a577..1a8745c 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -25,6 +25,7 @@
import android.os.Build;
import android.os.Process;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
@@ -137,7 +138,7 @@
if (!success) throw new Exception("Failed to start the relro file creator process");
} catch (Throwable t) {
// Log and discard errors as we must not crash the system server.
- Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
+ Slog.wtf(LOGTAG, "error starting relro file creator for abi " + abi, t);
crashHandler.run();
}
}
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl
index ec8b66d..33421ba 100644
--- a/core/java/android/window/IRemoteTransition.aidl
+++ b/core/java/android/window/IRemoteTransition.aidl
@@ -19,6 +19,7 @@
import android.view.SurfaceControl;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
/**
* Interface allowing remote processes to play transition animations.
@@ -61,6 +62,19 @@
in IRemoteTransitionFinishedCallback finishCallback);
/**
+ * Takes over the animation of the windows from an existing transition. Once complete, the
+ * implementation should call `finishCallback`.
+ *
+ * @param transition An identifier for the transition to be taken over.
+ * @param states The animation states of the windows involved in the transition. These must be
+ * sorted in the same way as the Changes inside `info`, and each state may be
+ * null.
+ */
+ void takeOverAnimation(in IBinder transition, in TransitionInfo info,
+ in SurfaceControl.Transaction t, in IRemoteTransitionFinishedCallback finishCallback,
+ in WindowAnimationState[] states);
+
+ /**
* Called when a different handler has consumed the transition
*
* @param transition An identifier for the transition that was consumed.
diff --git a/core/java/android/window/RemoteTransitionStub.java b/core/java/android/window/RemoteTransitionStub.java
index c9932ab..4a97b1e 100644
--- a/core/java/android/window/RemoteTransitionStub.java
+++ b/core/java/android/window/RemoteTransitionStub.java
@@ -31,6 +31,16 @@
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {}
+
+ @Override
+ public void takeOverAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ IRemoteTransitionFinishedCallback finishCallback,
+ WindowAnimationState[] states) throws RemoteException {
+ throw new RemoteException("Takeovers are not supported by this IRemoteTransition");
+ }
+
+
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted)
throws RemoteException {}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 93297e6..89327fe 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -18,10 +18,13 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.TestApi;
+import android.content.pm.ActivityInfo.ScreenOrientation;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
@@ -101,11 +104,20 @@
*/
private final boolean mAllowTransitionWhenEmpty;
+ /**
+ * The override orientation for the TaskFragment. This is effective only for a system organizer.
+ * The value is ignored otherwise. Default to {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ */
+ private final @ScreenOrientation int mOverrideOrientation;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty,
+ @ScreenOrientation int overrideOrientation) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -118,6 +130,7 @@
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ mOverrideOrientation = overrideOrientation;
}
@NonNull
@@ -168,6 +181,11 @@
return mAllowTransitionWhenEmpty;
}
+ /** @hide */
+ public @ScreenOrientation int getOverrideOrientation() {
+ return mOverrideOrientation;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -177,6 +195,7 @@
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
mAllowTransitionWhenEmpty = in.readBoolean();
+ mOverrideOrientation = in.readInt();
}
/** @hide */
@@ -190,6 +209,7 @@
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
dest.writeBoolean(mAllowTransitionWhenEmpty);
+ dest.writeInt(mOverrideOrientation);
}
@NonNull
@@ -217,6 +237,7 @@
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ + " overrideOrientation=" + mOverrideOrientation
+ "}";
}
@@ -252,6 +273,8 @@
private boolean mAllowTransitionWhenEmpty;
+ private @ScreenOrientation int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -330,12 +353,28 @@
return this;
}
+ /**
+ * Sets the override orientation for the TaskFragment. This is effective only for a system
+ * organizer. The value is ignored otherwise. Default to
+ * {@code SCREEN_ORIENTATION_UNSPECIFIED}.
+ *
+ * @see TaskFragmentOrganizer#registerOrganizer(boolean)
+ *
+ * @hide
+ */
+ @RequiresPermission(value = android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+ @NonNull
+ public Builder setOverrideOrientation(@ScreenOrientation int overrideOrientation) {
+ mOverrideOrientation = overrideOrientation;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken, mAllowTransitionWhenEmpty);
+ mPairedActivityToken, mAllowTransitionWhenEmpty, mOverrideOrientation);
}
}
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 994e732..bcae571 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -523,6 +523,9 @@
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
}
+ if ((flags & FLAG_CONFIG_AT_END) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("CONFIG_AT_END");
+ }
if ((flags & FLAG_MOVED_TO_TOP) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
}
diff --git a/core/java/android/window/WindowAnimationState.aidl b/core/java/android/window/WindowAnimationState.aidl
new file mode 100644
index 0000000..e662860
--- /dev/null
+++ b/core/java/android/window/WindowAnimationState.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * Properties of a window animation at a given point in time.
+ *
+ * {@hide}
+ */
+parcelable WindowAnimationState {
+ long timestamp;
+ RectF bounds;
+ float scale;
+ float topLeftRadius;
+ float topRightRadius;
+ float bottomRightRadius;
+ float bottomLeftRadius;
+ PointF velocityPxPerMs;
+}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bcbac93..47a4052 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -36,6 +36,8 @@
import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -75,14 +77,17 @@
@Nullable
private ImeBackAnimationController mImeBackAnimationController;
+ @GuardedBy("mLock")
/** Convenience hashmap to quickly decide if a callback has been added. */
private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
/** Holds all callbacks by priorities. */
@VisibleForTesting
+ @GuardedBy("mLock")
public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private Checker mChecker;
+ private final Object mLock = new Object();
public WindowOnBackInvokedDispatcher(@NonNull Context context) {
mChecker = new Checker(context);
@@ -94,20 +99,24 @@
*/
public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
@Nullable ImeBackAnimationController imeBackAnimationController) {
- mWindowSession = windowSession;
- mWindow = window;
- mImeBackAnimationController = imeBackAnimationController;
- if (!mAllCallbacks.isEmpty()) {
- setTopOnBackInvokedCallback(getTopCallback());
+ synchronized (mLock) {
+ mWindowSession = windowSession;
+ mWindow = window;
+ mImeBackAnimationController = imeBackAnimationController;
+ if (!mAllCallbacks.isEmpty()) {
+ setTopOnBackInvokedCallback(getTopCallback());
+ }
}
}
/** Detaches the dispatcher instance from its window. */
public void detachFromWindow() {
- clear();
- mWindow = null;
- mWindowSession = null;
- mImeBackAnimationController = null;
+ synchronized (mLock) {
+ clear();
+ mWindow = null;
+ mWindowSession = null;
+ mImeBackAnimationController = null;
+ }
}
// TODO: Take an Executor for the callback to run on.
@@ -125,65 +134,71 @@
*/
public void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
- if (mImeDispatcher != null) {
- mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
- return;
- }
- if (!mOnBackInvokedCallbacks.containsKey(priority)) {
- mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-
- // If callback has already been added, remove it and re-add it.
- if (mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+ return;
}
- Integer prevPriority = mAllCallbacks.get(callback);
- mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
- }
+ if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+ mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+ }
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- callbacks.add(callback);
- mAllCallbacks.put(callback, priority);
- if (previousTopCallback == null
- || (previousTopCallback != callback
- && mAllCallbacks.get(previousTopCallback) <= priority)) {
- setTopOnBackInvokedCallback(callback);
+ // If callback has already been added, remove it and re-add it.
+ if (mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback already added. Removing and re-adding it.");
+ }
+ Integer prevPriority = mAllCallbacks.get(callback);
+ mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
+ }
+
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ callbacks.add(callback);
+ mAllCallbacks.put(callback, priority);
+ if (previousTopCallback == null
+ || (previousTopCallback != callback
+ && mAllCallbacks.get(previousTopCallback) <= priority)) {
+ setTopOnBackInvokedCallback(callback);
+ }
}
}
@Override
public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
- if (mImeDispatcher != null) {
- mImeDispatcher.unregisterOnBackInvokedCallback(callback);
- return;
- }
- if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
- callback = mImeBackAnimationController;
- }
- if (!mAllCallbacks.containsKey(callback)) {
- if (DEBUG) {
- Log.i(TAG, "Callback not found. returning...");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+ return;
}
- return;
- }
- OnBackInvokedCallback previousTopCallback = getTopCallback();
- Integer priority = mAllCallbacks.get(callback);
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- callbacks.remove(callback);
- if (callbacks.isEmpty()) {
- mOnBackInvokedCallbacks.remove(priority);
- }
- mAllCallbacks.remove(callback);
- // Re-populate the top callback to WM if the removed callback was previously the top one.
- if (previousTopCallback == callback) {
- // We should call onBackCancelled() when an active callback is removed from dispatcher.
- sendCancelledIfInProgress(callback);
- setTopOnBackInvokedCallback(getTopCallback());
+ if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+ callback = mImeBackAnimationController;
+ }
+ if (!mAllCallbacks.containsKey(callback)) {
+ if (DEBUG) {
+ Log.i(TAG, "Callback not found. returning...");
+ }
+ return;
+ }
+ OnBackInvokedCallback previousTopCallback = getTopCallback();
+ Integer priority = mAllCallbacks.get(callback);
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ callbacks.remove(callback);
+ if (callbacks.isEmpty()) {
+ mOnBackInvokedCallbacks.remove(priority);
+ }
+ mAllCallbacks.remove(callback);
+ // Re-populate the top callback to WM if the removed callback was previously the top
+ // one.
+ if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from
+ // dispatcher.
+ sendCancelledIfInProgress(callback);
+ setTopOnBackInvokedCallback(getTopCallback());
+ }
}
}
@@ -191,15 +206,21 @@
* Indicates if the dispatcher is actively dispatching to a callback.
*/
public boolean isDispatching() {
- return mIsDispatching;
+ synchronized (mLock) {
+ return mIsDispatching;
+ }
}
private void onStartDispatching() {
- mIsDispatching = true;
+ synchronized (mLock) {
+ mIsDispatching = true;
+ }
}
private void onStopDispatching() {
- mIsDispatching = false;
+ synchronized (mLock) {
+ mIsDispatching = false;
+ }
}
private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
@@ -223,27 +244,29 @@
/** Clears all registered callbacks on the instance. */
public void clear() {
- if (mImeDispatcher != null) {
- mImeDispatcher.clear();
- mImeDispatcher = null;
- }
- if (!mAllCallbacks.isEmpty()) {
- OnBackInvokedCallback topCallback = getTopCallback();
- if (topCallback != null) {
- sendCancelledIfInProgress(topCallback);
- } else {
- // Should not be possible
- Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ synchronized (mLock) {
+ if (mImeDispatcher != null) {
+ mImeDispatcher.clear();
+ mImeDispatcher = null;
}
- // Clear binder references in WM.
- setTopOnBackInvokedCallback(null);
- }
+ if (!mAllCallbacks.isEmpty()) {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ if (topCallback != null) {
+ sendCancelledIfInProgress(topCallback);
+ } else {
+ // Should not be possible
+ Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ }
+ // Clear binder references in WM.
+ setTopOnBackInvokedCallback(null);
+ }
- // We should also stop running animations since all callbacks have been removed.
- // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
- Handler.getMain().post(mProgressAnimator::reset);
- mAllCallbacks.clear();
- mOnBackInvokedCallbacks.clear();
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
+ mAllCallbacks.clear();
+ mOnBackInvokedCallbacks.clear();
+ }
}
private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -275,13 +298,15 @@
}
public OnBackInvokedCallback getTopCallback() {
- if (mAllCallbacks.isEmpty()) {
- return null;
- }
- for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
- ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
- if (!callbacks.isEmpty()) {
- return callbacks.get(callbacks.size() - 1);
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ return null;
+ }
+ for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+ ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+ if (!callbacks.isEmpty()) {
+ return callbacks.get(callbacks.size() - 1);
+ }
}
}
return null;
@@ -315,16 +340,18 @@
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
writer.println(prefix + "WindowOnBackDispatcher:");
- if (mAllCallbacks.isEmpty()) {
- writer.println(prefix + "<None>");
- return;
- }
+ synchronized (mLock) {
+ if (mAllCallbacks.isEmpty()) {
+ writer.println(prefix + "<None>");
+ return;
+ }
- writer.println(innerPrefix + "Top Callback: " + getTopCallback());
- writer.println(innerPrefix + "Callbacks: ");
- mAllCallbacks.forEach((callback, priority) -> {
- writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
- });
+ writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+ writer.println(innerPrefix + "Callbacks: ");
+ mAllCallbacks.forEach((callback, priority) -> {
+ writer.println(innerPrefix + " Callback: " + callback + " Priority=" + priority);
+ });
+ }
}
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 4bd2d72..01920de 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -42,7 +42,11 @@
bool punchHoles(const char *filePath, const uint64_t offset,
const std::vector<Elf64_Phdr> &programHeaders) {
struct stat64 beforePunch;
- lstat64(filePath, &beforePunch);
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
uint64_t blockSize = beforePunch.st_blksize;
IF_ALOGD() {
ALOGD("Total number of LOAD segments %zu", programHeaders.size());
@@ -152,7 +156,10 @@
IF_ALOGD() {
struct stat64 afterPunch;
- lstat64(filePath, &afterPunch);
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
"",
afterPunch.st_blocks, afterPunch.st_blksize,
@@ -177,7 +184,7 @@
// only consider elf64 for punching holes
if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
- ALOGE("Provided file is not ELF64");
+ ALOGW("Provided file is not ELF64");
return false;
}
@@ -215,4 +222,108 @@
return punchHoles(filePath, offset, programHeaders);
}
+bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
+ android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
+ ALOGE("Can't open file to punch %s", filePath);
+ return false;
+ }
+
+ struct stat64 beforePunch;
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
+ uint64_t blockSize = beforePunch.st_blksize;
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
+ static_cast<uint64_t>(beforePunch.st_size));
+ }
+
+ if (extraFieldLen < blockSize) {
+ ALOGD("Skipping punching apk as extra field length is less than block size");
+ return false;
+ }
+
+ // content is preceded by extra field. Zip offset is offset of exact content.
+ // move back by extraFieldLen so that scan can be started at start of extra field.
+ uint64_t extraFieldStart;
+ if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
+ ALOGE("Overflow occurred when calculating start of extra field");
+ return false;
+ }
+
+ constexpr uint64_t kMaxSize = 64 * 1024;
+ // Use malloc to gracefully handle any oom conditions
+ std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
+ &free);
+ if (buffer == nullptr) {
+ ALOGE("Failed to allocate read buffer");
+ return false;
+ }
+
+ // Read the entire extra fields at once and punch file according to zero stretches.
+ if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
+ ALOGE("Failed to read extra field content");
+ return false;
+ }
+
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
+ HexString(buffer.get(), extraFieldLen).c_str());
+ }
+
+ uint64_t currentSize = 0;
+ while (currentSize < extraFieldLen) {
+ uint64_t end = currentSize;
+ // find zero ranges
+ while (end < extraFieldLen && *(buffer.get() + end) == 0) {
+ ++end;
+ }
+
+ uint64_t punchLen;
+ if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
+ ALOGW("Overflow occurred when calculating punching length");
+ return false;
+ }
+
+ // Don't punch for every stretch of zero which is found
+ if (punchLen > blockSize) {
+ uint64_t punchOffset;
+ if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
+ ALOGW("Overflow occurred when calculating punch start offset");
+ return false;
+ }
+
+ ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen);
+
+ // Punch hole for this entire stretch.
+ int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset,
+ punchLen);
+ if (result < 0) {
+ ALOGE("fallocate failed to punch hole inside apk, error:%d", errno);
+ return false;
+ }
+ }
+ currentSize = end;
+ ++currentSize;
+ }
+
+ IF_ALOGD() {
+ struct stat64 afterPunch;
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+ ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ afterPunch.st_blocks, afterPunch.st_blksize,
+ static_cast<uint64_t>(afterPunch.st_size));
+ }
+ return true;
+}
+
}; // namespace android
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
index a6b145c..52445e2 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.h
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -28,4 +28,11 @@
*/
bool punchHolesInElf64(const char* filePath, uint64_t offset);
+/*
+ * This function punches holes in zero segments of Apk file which are introduced during the
+ * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in
+ * extra field for zero stretches till the actual file content.
+ */
+bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
+
} // namespace android
\ No newline at end of file
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index faa83f8..9b8dab7 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -28,6 +28,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -145,8 +146,9 @@
uint16_t method;
off64_t offset;
-
- if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
+ uint16_t extraFieldLength;
+ if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+ &extraFieldLength)) {
ALOGE("Couldn't read zip entry info\n");
return INSTALL_FAILED_INVALID_APK;
}
@@ -177,6 +179,12 @@
"%" PRIu64 "",
fileName, zipFile->getZipFileName(), offset);
}
+
+ // if extra field for this zip file is present with some length, possibility is that it is
+ // padding added for zip alignment. Punch holes there too.
+ if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+ ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+ }
#endif // ENABLE_PUNCH_HOLES
return INSTALL_SUCCEEDED;
@@ -279,6 +287,25 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
+#ifdef ENABLE_PUNCH_HOLES
+ // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
+ // will be useful for ext4 based systems
+ struct statfs64 fsInfo;
+ int result = statfs64(localFileName, &fsInfo);
+ if (result < 0) {
+ ALOGW("Failed to stat file :%s", localFileName);
+ }
+
+ if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
+ ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
+ static_cast<uint64_t>(fsInfo.f_type));
+ if (!punchHolesInElf64(localFileName, 0)) {
+ ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
+ zipFile->getZipFileName());
+ }
+ }
+#endif // ENABLE_PUNCH_HOLES
+
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
return INSTALL_SUCCEEDED;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c694426..0864210 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -581,6 +581,7 @@
<protected-broadcast android:name="android.app.action.KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED" />
<protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
+ <protected-broadcast android:name="com.android.server.notification.TimeToLiveHelper" />
<protected-broadcast android:name="NotificationHistoryDatabase.CLEANUP" />
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="EventConditionProvider.EVALUATE" />
@@ -5889,7 +5890,7 @@
<!-- Allows an application to subscribe to notifications about the nearby devices' presence
status change base on the UUIDs.
<p>Not for use by third-party applications.</p>
- @FlaggedApi("android.companion.flags.device_presence")
+ @FlaggedApi("android.companion.device_presence")
-->
<permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable/ic_private_profile_badge.xml b/core/res/res/drawable/ic_private_profile_badge.xml
index b042c39..8f7bbb5 100644
--- a/core/res/res/drawable/ic_private_profile_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_badge.xml
@@ -20,6 +20,10 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
android:fillColor="#3C4043"/>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_private_profile_icon_badge.xml b/core/res/res/drawable/ic_private_profile_icon_badge.xml
index 5f1f1b7..4143c3b 100644
--- a/core/res/res/drawable/ic_private_profile_icon_badge.xml
+++ b/core/res/res/drawable/ic_private_profile_icon_badge.xml
@@ -24,8 +24,12 @@
android:scaleY=".66"
android:translateX="42"
android:translateY="42">
- <path
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"
- android:fillColor="#3C4043"/>
+ <path
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
+ android:fillColor="#3C4043"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+ android:fillColor="#3C4043"/>
</group>
</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/stat_sys_private_profile_status.xml b/core/res/res/drawable/stat_sys_private_profile_status.xml
index 429070e..958a4d7 100644
--- a/core/res/res/drawable/stat_sys_private_profile_status.xml
+++ b/core/res/res/drawable/stat_sys_private_profile_status.xml
@@ -20,6 +20,10 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
+ android:pathData="M12,2L4,5V11.09C4,16.14 7.41,20.85 12,22C16.59,20.85 20,16.14 20,11.09V5L12,2ZM15,15V17H13V18H11V12.84C9.56,12.41 8.5,11.09 8.5,9.5C8.5,7.57 10.07,6 12,6C13.93,6 15.5,7.57 15.5,9.5C15.5,11.08 14.44,12.41 13,12.84V15H15Z"
android:fillColor="@android:color/white"
- android:pathData="M5,3H19C20.1,3 21,3.9 21,5V19C21,20.1 20.1,21 19,21H5C3.9,21 3,20.1 3,19V5C3,3.9 3.9,3 5,3ZM13.5,15.501L12.93,12.271C13.57,11.941 14,11.271 14,10.501C14,9.401 13.1,8.501 12,8.501C10.9,8.501 10,9.401 10,10.501C10,11.271 10.43,11.941 11.07,12.271L10.5,15.501H13.5Z"/>
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M12,11C12.828,11 13.5,10.328 13.5,9.5C13.5,8.672 12.828,8 12,8C11.172,8 10.5,8.672 10.5,9.5C10.5,10.328 11.172,11 12,11Z"
+ android:fillColor="@android:color/white"/>
</vector>
\ No newline at end of file
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 404e873..04e90ba 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -154,6 +154,12 @@
"android.test.runner",
"org.apache.http.legacy",
],
+ uses_libs: [
+ "android.test.runner",
+ ],
+ optional_uses_libs: [
+ "org.apache.http.legacy",
+ ],
sdk_version: "core_platform",
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 28343f1..0bf9a4c 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -203,7 +203,9 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.setFrameContentVelocity(1_000_000_000f);
mMovingView.invalidate();
- runAfterDraw(() -> assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> {
+ assertEquals(140f, mViewRoot.getLastPreferredFrameRate(), 0f);
+ });
});
waitForAfterDraw();
}
@@ -411,6 +413,26 @@
waitForAfterDraw();
}
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY,
+ FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY
+ })
+ public void frameRateAndCategory() throws Throwable {
+ waitForFrameRateCategoryToSettle();
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mMovingView.setFrameContentVelocity(1f);
+ mMovingView.invalidate();
+ runAfterDraw(() -> {
+ assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRoot.getLastPreferredFrameRateCategory());
+ assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
+ });
+ });
+ waitForAfterDraw();
+ }
+
private void runAfterDraw(@NonNull Runnable runnable) {
Handler handler = new Handler(Looper.getMainLooper());
mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index e6e7e8c..80fef6c 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -22,6 +22,7 @@
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -51,6 +52,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -495,13 +497,13 @@
@UiThreadTest
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_getDefaultValues() {
ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ viewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(0, viewRootImpl.getLastPreferredFrameRate(), 0.1);
}
/**
@@ -513,7 +515,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() throws Throwable {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -538,7 +540,7 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+ assertTrue(mViewRootImpl.getIsFrameRateBoosting());
});
}
@@ -550,7 +552,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() throws Throwable {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -568,8 +570,8 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -582,7 +584,7 @@
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() throws Throwable {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -655,7 +657,7 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getIsFrameRateBoosting(), true);
+ assertTrue(mViewRootImpl.getIsFrameRateBoosting());
});
waitForFrameRateCategoryToSettle(mView);
@@ -750,14 +752,14 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
View mView1 = new View(sContext);
attachViewToWindow(mView1);
ViewRootImpl viewRootImpl = mView1.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ viewRootImpl.getPreferredFrameRateCategory());
});
// reset the frame rate category counts
@@ -771,20 +773,20 @@
sInstrumentation.runOnMainSync(() -> {
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+ assertEquals(FRAME_RATE_CATEGORY_LOW, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ assertEquals(FRAME_RATE_CATEGORY_NORMAL, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT,
+ viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW, 0, null);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH, viewRootImpl.getPreferredFrameRateCategory());
});
}
@@ -796,55 +798,67 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRate_aggregate() {
mView = new View(sContext);
attachViewToWindow(mView);
mViewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
assertEquals(mViewRootImpl.getFrameRateCompatibility(),
FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(24, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ mViewRootImpl.getFrameRateCompatibility());
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getPreferredFrameRateCategory());
+ }
mViewRootImpl.votePreferredFrameRate(30, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 30, 0.1);
+ assertEquals(30, mViewRootImpl.getPreferredFrameRate(), 0.1);
// If there is a conflict, then set compatibility to
// FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- // Should be true since there is a conflict between 24 and 30.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), true);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ // Should be true since there is a conflict between 24 and 30.
+ assertTrue(mViewRootImpl.isFrameRateConflicted());
+ }
+
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 60, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(60, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_GTE,
+ mViewRootImpl.getFrameRateCompatibility());
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getPreferredFrameRateCategory());
+ }
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(120, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
// Should be false since 60 is a divisor of 120.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
mViewRootImpl.votePreferredFrameRate(60, FRAME_RATE_COMPATIBILITY_GTE);
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 120, 0.1);
+ assertEquals(120, mViewRootImpl.getPreferredFrameRate(), 0.1);
// compatibility should be remained the same (FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
// since the frame rate 60 is smaller than 120.
- assertEquals(mViewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ mViewRootImpl.getFrameRateCompatibility());
// Should be false since 60 is a divisor of 120.
- assertEquals(mViewRootImpl.isFrameRateConflicted(), false);
-
+ assertFalse(mViewRootImpl.isFrameRateConflicted());
});
}
@@ -864,14 +878,14 @@
mViewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
});
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -880,24 +894,24 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -928,22 +942,23 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ assertEquals(0, mViewRootImpl.getPreferredFrameRate(), 0.1);
mView.setFrameContentVelocity(100);
mView.invalidate();
- runAfterDraw(() -> assertTrue(mViewRootImpl.getLastPreferredFrameRate() > 0));
+ runAfterDraw(() -> {
+ if (toolkitFrameRateVelocityMappingReadOnly()) {
+ assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
+ } else {
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(0, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
+ }
+ });
});
waitForAfterDraw();
sInstrumentation.waitForIdleSync();
- if (toolkitFrameRateVelocityMappingReadOnly()) {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
- assertTrue(mViewRootImpl.getLastPreferredFrameRate() >= 60f);
- } else {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
- assertEquals(mViewRootImpl.getLastPreferredFrameRate(), 0, 0.1);
- }
}
/**
@@ -951,7 +966,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_insetsAnimation() {
mView = new View(sContext);
WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
@@ -977,8 +992,8 @@
sInstrumentation.waitForIdleSync();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(FRAME_RATE_CATEGORY_HIGH,
+ viewRootImpl.getLastPreferredFrameRateCategory());
});
}
@@ -988,7 +1003,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_frameRateBoostOnTouch() {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -996,9 +1011,9 @@
ViewRootImpl viewRootImpl = mView.getViewRootImpl();
final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
- assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
- assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
- attrs.getFrameRateBoostOnTouchEnabled());
+ assertTrue(attrs.getFrameRateBoostOnTouchEnabled());
+ assertEquals(attrs.getFrameRateBoostOnTouchEnabled(),
+ viewRootImpl.getFrameRateBoostOnTouchEnabled());
sInstrumentation.runOnMainSync(() -> {
attrs.setFrameRateBoostOnTouchEnabled(false);
@@ -1008,9 +1023,9 @@
sInstrumentation.runOnMainSync(() -> {
final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
- assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
- assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
- newAttrs.getFrameRateBoostOnTouchEnabled());
+ assertFalse(newAttrs.getFrameRateBoostOnTouchEnabled());
+ assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(),
+ viewRootImpl.getFrameRateBoostOnTouchEnabled());
});
}
@@ -1021,7 +1036,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
final long delay = 200L;
@@ -1031,27 +1046,27 @@
ViewRootImpl viewRootImpl = mView.getViewRootImpl();
sInstrumentation.runOnMainSync(() -> {
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
viewRootImpl.votePreferredFrameRate(24, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
mView.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRate(), 24, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(24, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
});
Thread.sleep(delay);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- assertEquals(viewRootImpl.getFrameRateCompatibility(),
- FRAME_RATE_COMPATIBILITY_FIXED_SOURCE);
- assertEquals(viewRootImpl.isFrameRateConflicted(), false);
+ assertEquals(0, viewRootImpl.getPreferredFrameRate(), 0.1);
+ assertEquals(FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ viewRootImpl.getFrameRateCompatibility());
+ assertFalse(viewRootImpl.isFrameRateConflicted());
}
/**
@@ -1070,15 +1085,16 @@
mViewRootImpl = mView.getViewRootImpl();
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
mView.setRequestedFrameRate(frameRate);
mView.invalidate();
runAfterDraw(() -> {
- assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(mViewRootImpl.getLastPreferredFrameRate(), frameRate, 0.1);
+ int expected = toolkitFrameRateDefaultNormalReadOnly()
+ ? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
+ assertEquals(expected, mViewRootImpl.getLastPreferredFrameRateCategory());
+ assertEquals(frameRate, mViewRootImpl.getLastPreferredFrameRate(), 0.1);
});
});
waitForAfterDraw();
@@ -1086,17 +1102,17 @@
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
}
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_LOW);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_LOW));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_LOW,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1146,7 +1162,7 @@
// reset the frame rate category counts
for (int i = 0; i < 5; i++) {
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -1155,28 +1171,30 @@
// In transition from frequent update to infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE));
+ runAfterDraw(() -> {
+ assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRootImpl.getLastPreferredFrameRateCategory());
+ });
});
waitForAfterDraw();
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
// Infrequent update
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1186,7 +1204,7 @@
*/
@Test
@RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
- FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY})
+ FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
mView = new View(sContext);
attachViewToWindow(mView);
@@ -1194,9 +1212,9 @@
ViewRootImpl viewRoot = mView.getViewRootImpl();
final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
- assertEquals(attrs.isFrameRatePowerSavingsBalanced(), true);
- assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
- attrs.isFrameRatePowerSavingsBalanced());
+ assertTrue(attrs.isFrameRatePowerSavingsBalanced());
+ assertEquals(attrs.isFrameRatePowerSavingsBalanced(),
+ viewRoot.isFrameRatePowerSavingsBalanced());
sInstrumentation.runOnMainSync(() -> {
attrs.setFrameRatePowerSavingsBalanced(false);
@@ -1206,9 +1224,9 @@
sInstrumentation.runOnMainSync(() -> {
final WindowManager.LayoutParams newAttrs = viewRoot.mWindowAttributes;
- assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(), false);
- assertEquals(viewRoot.isFrameRatePowerSavingsBalanced(),
- newAttrs.isFrameRatePowerSavingsBalanced());
+ assertFalse(newAttrs.isFrameRatePowerSavingsBalanced());
+ assertEquals(newAttrs.isFrameRatePowerSavingsBalanced(),
+ viewRoot.isFrameRatePowerSavingsBalanced());
});
}
@@ -1243,8 +1261,8 @@
waitForFrameRateCategoryToSettle(mView);
sInstrumentation.runOnMainSync(() -> {
- assertEquals(mViewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
+ mViewRootImpl.getPreferredFrameRateCategory());
mView.invalidate();
int expected = toolkitFrameRateDefaultNormalReadOnly()
? FRAME_RATE_CATEGORY_NORMAL : FRAME_RATE_CATEGORY_HIGH;
@@ -1259,7 +1277,7 @@
for (int i = 0; i < 5; i++) {
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
mView.invalidate();
});
sInstrumentation.waitForIdleSync();
@@ -1267,10 +1285,10 @@
Thread.sleep(delay);
sInstrumentation.runOnMainSync(() -> {
- mView.setRequestedFrameRate(mView.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
+ mView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_DEFAULT);
mView.invalidate();
- runAfterDraw(() -> assertEquals(mViewRootImpl.getLastPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL));
+ runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_NORMAL,
+ mViewRootImpl.getLastPreferredFrameRateCategory()));
});
waitForAfterDraw();
}
@@ -1403,7 +1421,7 @@
mViewRootImpl.dispatchInputEvent(event);
});
sInstrumentation.waitForIdleSync();
- assertEquals(mKeyReceived, shouldReceiveKey);
+ assertEquals(shouldReceiveKey, mKeyReceived);
}
private void attachViewToWindow(View view) {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2f2215f..d1d7c14 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,8 +16,6 @@
package android.security;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* This class provides some constants and helper methods related to Android's Keystore service.
* This class was originally much larger, but its functionality was superseded by other classes.
@@ -30,11 +28,4 @@
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
-
- private static final KeyStore KEY_STORE = new KeyStore();
-
- @UnsupportedAppUsage
- public static KeyStore getInstance() {
- return KEY_STORE;
- }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index b8ac191..0a5a81b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -25,8 +25,8 @@
import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
-import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
-import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.DividerAttributes.RATIO_SYSTEM_DEFAULT;
+import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_SYSTEM_DEFAULT;
import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
@@ -64,6 +64,9 @@
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -76,12 +79,13 @@
* Manages the rendering and interaction of the divider.
*/
class DividerPresenter implements View.OnTouchListener {
+ static final float RATIO_EXPANDED_PRIMARY = 1.0f;
+ static final float RATIO_EXPANDED_SECONDARY = 0.0f;
private static final String WINDOW_NAME = "AE Divider";
private static final int VEIL_LAYER = 0;
private static final int DIVIDER_LAYER = 1;
// TODO(b/327067596) Update based on UX guidance.
- private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK);
private static final Color DEFAULT_PRIMARY_VEIL_COLOR = Color.valueOf(Color.BLACK);
private static final Color DEFAULT_SECONDARY_VEIL_COLOR = Color.valueOf(Color.GRAY);
@VisibleForTesting
@@ -162,54 +166,55 @@
return;
}
+ final SplitAttributes splitAttributes = topSplitContainer.getCurrentSplitAttributes();
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+
// Clean up the decor surface if DividerAttributes is null.
- final DividerAttributes dividerAttributes =
- topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
if (dividerAttributes == null) {
removeDecorSurfaceAndDivider(wct);
return;
}
- if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
- instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
- // No divider is needed for ExpandContainersSplitType.
- removeDivider();
- return;
- }
+ // At this point, a divider is required.
- // Skip updating when the TFs have not been updated to match the SplitAttributes.
- if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
- || topSplitContainer.getSecondaryContainer().getLastRequestedBounds()
- .isEmpty()) {
- return;
- }
-
+ // Create the decor surface if one is not available yet.
final SurfaceControl decorSurface = parentInfo.getDecorSurface();
if (decorSurface == null) {
// Clean up when the decor surface is currently unavailable.
removeDivider();
// Request to create the decor surface
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer());
return;
}
- // make the top primary container the owner of the decor surface.
- if (!Objects.equals(mDecorSurfaceOwner,
- topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
- createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+ // Update the decor surface owner if needed.
+ boolean isDraggableExpandType =
+ SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+ final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType
+ ? topSplitContainer.getSecondaryContainer()
+ : topSplitContainer.getPrimaryContainer();
+
+ if (!Objects.equals(
+ mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) {
+ createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer);
}
+ final boolean isVerticalSplit = isVerticalSplit(topSplitContainer);
+ final boolean isReversedLayout = isReversedLayout(
+ topSplitContainer.getCurrentSplitAttributes(),
+ parentInfo.getConfiguration());
updateProperties(
new Properties(
parentInfo.getConfiguration(),
dividerAttributes,
decorSurface,
- getInitialDividerPosition(topSplitContainer),
- isVerticalSplit(topSplitContainer),
- isReversedLayout(
- topSplitContainer.getCurrentSplitAttributes(),
- parentInfo.getConfiguration()),
- parentInfo.getDisplayId()));
+ getInitialDividerPosition(
+ topSplitContainer, isVerticalSplit, isReversedLayout),
+ isVerticalSplit,
+ isReversedLayout,
+ parentInfo.getDisplayId(),
+ isDraggableExpandType
+ ));
}
}
@@ -242,14 +247,21 @@
*
* See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}.
*/
- @GuardedBy("mLock")
- private void createOrMoveDecorSurface(
+ void createOrMoveDecorSurface(
@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+ synchronized (mLock) {
+ createOrMoveDecorSurfaceLocked(wct, container);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void createOrMoveDecorSurfaceLocked(
+ @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+ mDecorSurfaceOwner = container.getTaskFragmentToken();
final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE)
.build();
- wct.addTaskFragmentOperation(container.getTaskFragmentToken(), operation);
- mDecorSurfaceOwner = container.getTaskFragmentToken();
+ wct.addTaskFragmentOperation(mDecorSurfaceOwner, operation);
}
@GuardedBy("mLock")
@@ -274,15 +286,28 @@
}
@VisibleForTesting
- static int getInitialDividerPosition(@NonNull SplitContainer splitContainer) {
+ static int getInitialDividerPosition(
+ @NonNull SplitContainer splitContainer,
+ boolean isVerticalSplit,
+ boolean isReversedLayout) {
final Rect primaryBounds =
splitContainer.getPrimaryContainer().getLastRequestedBounds();
final Rect secondaryBounds =
splitContainer.getSecondaryContainer().getLastRequestedBounds();
- if (isVerticalSplit(splitContainer)) {
- return Math.min(primaryBounds.right, secondaryBounds.right);
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+
+ if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) {
+ // If the container is fully expanded by dragging the divider, we display the divider
+ // on the edge.
+ final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes());
+ final int fullyExpandedPosition = isVerticalSplit
+ ? primaryBounds.right - dividerWidth
+ : primaryBounds.bottom - dividerWidth;
+ return isReversedLayout ? fullyExpandedPosition : 0;
} else {
- return Math.min(primaryBounds.bottom, secondaryBounds.bottom);
+ return isVerticalSplit
+ ? Math.min(primaryBounds.right, secondaryBounds.right)
+ : Math.min(primaryBounds.bottom, secondaryBounds.bottom);
}
}
@@ -359,14 +384,14 @@
@VisibleForTesting
static int getBoundsOffsetForDivider(
int dividerWidthPx,
- @NonNull SplitAttributes.SplitType splitType,
+ @NonNull SplitType splitType,
@SplitPresenter.ContainerPosition int position) {
- if (splitType instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
- // No divider is needed for the ExpandContainersSplitType.
+ if (splitType instanceof ExpandContainersSplitType) {
+ // No divider offset is needed for the ExpandContainersSplitType.
return 0;
}
int primaryOffset;
- if (splitType instanceof final SplitAttributes.SplitType.RatioSplitType splitRatio) {
+ if (splitType instanceof final RatioSplitType splitRatio) {
// When a divider is present, both containers shrink by an amount proportional to their
// split ratio and sum to the width of the divider, so that the ending sizing of the
// containers still maintain the same ratio.
@@ -393,7 +418,8 @@
* Sanitizes and sets default values in the {@link DividerAttributes}.
*
* Unset values will be set with system default values. See
- * {@link DividerAttributes#WIDTH_UNSET} and {@link DividerAttributes#RATIO_UNSET}.
+ * {@link DividerAttributes#WIDTH_SYSTEM_DEFAULT} and
+ * {@link DividerAttributes#RATIO_SYSTEM_DEFAULT}.
*
* @param dividerAttributes input {@link DividerAttributes}
* @return a {@link DividerAttributes} that has all values properly set.
@@ -405,7 +431,7 @@
return null;
}
int widthDp = dividerAttributes.getWidthDp();
- if (widthDp == WIDTH_UNSET) {
+ if (widthDp == WIDTH_SYSTEM_DEFAULT) {
widthDp = DEFAULT_DIVIDER_WIDTH_DP;
}
@@ -416,12 +442,12 @@
}
float minRatio = dividerAttributes.getPrimaryMinRatio();
- if (minRatio == RATIO_UNSET) {
+ if (minRatio == RATIO_SYSTEM_DEFAULT) {
minRatio = DEFAULT_MIN_RATIO;
}
float maxRatio = dividerAttributes.getPrimaryMaxRatio();
- if (maxRatio == RATIO_UNSET) {
+ if (maxRatio == RATIO_SYSTEM_DEFAULT) {
maxRatio = DEFAULT_MAX_RATIO;
}
@@ -438,7 +464,7 @@
final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
mDividerPosition = calculateDividerPosition(
event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
- mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
mRenderer.setDividerPosition(mDividerPosition);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
@@ -456,23 +482,27 @@
}
}
- // Returns false so that the default button click callback is still triggered, i.e. the
- // button UI transitions into the "pressed" state.
- return false;
+ // Returns true to prevent the default button click callback. The button pressed state is
+ // set/unset when starting/finishing dragging.
+ return true;
}
@GuardedBy("mLock")
private void onStartDragging() {
mRenderer.mIsDragging = true;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mRenderer.updateSurface(t);
mRenderer.showVeils(t);
- final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
mCallbackExecutor.execute(() -> {
mDragEventCallback.onStartDragging(
- wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, true /* boosted */, t));
+ wct -> {
+ synchronized (mLock) {
+ setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, true /* boosted */, t);
+ }
+ });
});
}
@@ -485,18 +515,62 @@
@GuardedBy("mLock")
private void onFinishDragging() {
+ mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
+ mRenderer.setDividerPosition(mDividerPosition);
+
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mRenderer.updateSurface(t);
mRenderer.hideVeils(t);
- final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
// Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+ // mDecorSurfaceOwner may change between here and when the callback is executed,
+ // e.g. when the decor surface owner becomes the secondary container when it is expanded to
+ // fullscreen.
mCallbackExecutor.execute(() -> {
mDragEventCallback.onFinishDragging(
mTaskId,
- wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, false /* boosted */, t));
+ wct -> {
+ synchronized (mLock) {
+ setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, false /* boosted */, t);
+ }
+ });
});
mRenderer.mIsDragging = false;
+ mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+ }
+
+ /**
+ * Returns the divider position adjusted for the min max ratio and fullscreen expansion.
+ *
+ * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
+ * {@link DividerAttributes#getPrimaryMinRatio()} and
+ * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
+ * choose a snap algorithm to adjust the ending position to either fully expand one container or
+ * move the divider back to the specified min/max ratio.
+ *
+ * TODO(b/327067596) implement snap algorithm
+ *
+ * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
+ * for expanded right (bottom) container, or task width (height) minus the divider width for
+ * expanded left (top) container.
+ */
+ @GuardedBy("mLock")
+ private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+ final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+ final int minPosition = calculateMinPosition();
+ final int maxPosition = calculateMaxPosition();
+ final int fullyExpandedPosition = mProperties.mIsVerticalSplit
+ ? taskBounds.right - mRenderer.mDividerWidthPx
+ : taskBounds.bottom - mRenderer.mDividerWidthPx;
+ if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
+ if (dividerPosition < minPosition) {
+ return 0;
+ }
+ if (dividerPosition > maxPosition) {
+ return fullyExpandedPosition;
+ }
+ }
+ return Math.clamp(dividerPosition, minPosition, maxPosition);
}
private static void setDecorSurfaceBoosted(
@@ -520,7 +594,7 @@
@VisibleForTesting
static int calculateDividerPosition(@NonNull MotionEvent event, @NonNull Rect taskBounds,
int dividerWidthPx, @NonNull DividerAttributes dividerAttributes,
- boolean isVerticalSplit, boolean isReversedLayout) {
+ boolean isVerticalSplit, int minPosition, int maxPosition) {
// The touch event is in display space. Converting it into the task window space.
final int touchPositionInTaskSpace = isVerticalSplit
? (int) (event.getRawX()) - taskBounds.left
@@ -530,15 +604,31 @@
// position is offset by half of the divider width.
int dividerPosition = touchPositionInTaskSpace - dividerWidthPx / 2;
- // Limit the divider position to the min and max ratios set in DividerAttributes.
- // TODO(b/327536303) Handle when the divider is dragged to the edge.
- dividerPosition = Math.max(dividerPosition, calculateMinPosition(
- taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
- dividerPosition = Math.min(dividerPosition, calculateMaxPosition(
- taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+ // If dragging to fullscreen is not allowed, limit the divider position to the min and max
+ // ratios set in DividerAttributes. Otherwise, dragging beyond the min and max ratios is
+ // temporarily allowed and the final ratio will be adjusted in onFinishDragging.
+ if (!isDraggingToFullscreenAllowed(dividerAttributes)) {
+ dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+ }
return dividerPosition;
}
+ @GuardedBy("mLock")
+ private int calculateMinPosition() {
+ return calculateMinPosition(
+ mProperties.mConfiguration.windowConfiguration.getBounds(),
+ mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+ mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ }
+
+ @GuardedBy("mLock")
+ private int calculateMaxPosition() {
+ return calculateMaxPosition(
+ mProperties.mConfiguration.windowConfiguration.getBounds(),
+ mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+ mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+ }
+
/** Calculates the min position of the divider that the user is allowed to drag to. */
@VisibleForTesting
static int calculateMinPosition(@NonNull Rect taskBounds, int dividerWidthPx,
@@ -581,13 +671,24 @@
mProperties.mConfiguration.windowConfiguration.getBounds(),
mRenderer.mDividerWidthPx,
mProperties.mIsVerticalSplit,
- mProperties.mIsReversedLayout);
+ mProperties.mIsReversedLayout,
+ calculateMinPosition(),
+ calculateMaxPosition(),
+ isDraggingToFullscreenAllowed(mProperties.mDividerAttributes));
}
}
+ private static boolean isDraggingToFullscreenAllowed(
+ @NonNull DividerAttributes dividerAttributes) {
+ // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
+ // updated.
+ return true;
+ }
+
/**
* Returns the new split ratio of the {@link SplitContainer} based on the current divider
* position.
+ *
* @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
* @param dividerPosition the divider position. See {@link #mDividerPosition}.
* @param taskBounds the task bounds
@@ -599,7 +700,9 @@
* bottom-to-top. If {@code false}, the split is not reversed, i.e.
* left-to-right or top-to-bottom. See
* {@link SplitAttributesHelper#isReversedLayout}
- * @return the computed split ratio of the primary container.
+ * @return the computed split ratio of the primary container. If the primary container is fully
+ * expanded, {@link #RATIO_EXPANDED_PRIMARY} is returned. If the secondary container is fully
+ * expanded, {@link #RATIO_EXPANDED_SECONDARY} is returned.
*/
@VisibleForTesting
static float calculateNewSplitRatio(
@@ -608,15 +711,33 @@
@NonNull Rect taskBounds,
int dividerWidthPx,
boolean isVerticalSplit,
- boolean isReversedLayout) {
+ boolean isReversedLayout,
+ int minPosition,
+ int maxPosition,
+ boolean isDraggingToFullscreenAllowed) {
+
+ // Handle the fully expanded cases.
+ if (isDraggingToFullscreenAllowed) {
+ // The divider position is already adjusted by the snap algorithm in onFinishDragging.
+ // If the divider position is not in the range [minPosition, maxPosition], then one of
+ // the containers is fully expanded.
+ if (dividerPosition < minPosition) {
+ return isReversedLayout ? RATIO_EXPANDED_PRIMARY : RATIO_EXPANDED_SECONDARY;
+ }
+ if (dividerPosition > maxPosition) {
+ return isReversedLayout ? RATIO_EXPANDED_SECONDARY : RATIO_EXPANDED_PRIMARY;
+ }
+ } else {
+ dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+ }
+
+ final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
+ final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
final int usableSize = isVerticalSplit
? taskBounds.width() - dividerWidthPx
: taskBounds.height() - dividerWidthPx;
- final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
- final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
-
- float newRatio;
+ final float newRatio;
if (isVerticalSplit) {
final int newPrimaryWidth = isReversedLayout
? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
@@ -677,6 +798,7 @@
private final int mDisplayId;
private final boolean mIsReversedLayout;
+ private final boolean mIsDraggableExpandType;
@VisibleForTesting
Properties(
@@ -686,7 +808,8 @@
int initialDividerPosition,
boolean isVerticalSplit,
boolean isReversedLayout,
- int displayId) {
+ int displayId,
+ boolean isDraggableExpandType) {
mConfiguration = configuration;
mDividerAttributes = dividerAttributes;
mDecorSurface = decorSurface;
@@ -694,6 +817,7 @@
mIsVerticalSplit = isVerticalSplit;
mIsReversedLayout = isReversedLayout;
mDisplayId = displayId;
+ mIsDraggableExpandType = isDraggableExpandType;
}
/**
@@ -714,7 +838,8 @@
&& a.mInitialDividerPosition == b.mInitialDividerPosition
&& a.mIsVerticalSplit == b.mIsVerticalSplit
&& a.mDisplayId == b.mDisplayId
- && a.mIsReversedLayout == b.mIsReversedLayout;
+ && a.mIsReversedLayout == b.mIsReversedLayout
+ && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
}
private static boolean areSameSurfaces(
@@ -761,6 +886,7 @@
private SurfaceControl mSecondaryVeil;
private boolean mIsDragging;
private int mDividerPosition;
+ private View mDragHandle;
private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
mProperties = properties;
@@ -857,6 +983,7 @@
PixelFormat.TRANSLUCENT);
lp.setTitle(WINDOW_NAME);
mViewHost.setView(mDividerLayout, lp);
+ mViewHost.relayout(lp);
}
/**
@@ -867,7 +994,12 @@
*/
private void updateDivider(@NonNull SurfaceControl.Transaction t) {
mDividerLayout.removeAllViews();
- mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb());
+ if (mProperties.mIsDraggableExpandType) {
+ // If a container is fully expanded, the divider overlays on the expanded container.
+ mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+ } else {
+ mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+ }
if (mProperties.mDividerAttributes.getDividerType()
== DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
createVeils();
@@ -916,6 +1048,7 @@
}
button.setOnTouchListener(mListener);
+ mDragHandle = button;
mDividerLayout.addView(button);
}
@@ -928,7 +1061,7 @@
.setHidden(!visible)
.setCallsite("DividerManager.createChildSurface")
.setBufferSize(bounds.width(), bounds.height())
- .setColorLayer()
+ .setEffectLayer()
.build();
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
index 042a68a6..4541a84 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
@@ -20,6 +20,7 @@
import android.view.View;
import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
/** Helper functions for {@link SplitAttributes} */
class SplitAttributesHelper {
@@ -43,4 +44,17 @@
"Invalid layout direction:" + splitAttributes.getLayoutDirection());
}
}
+
+ /**
+ * Returns whether the {@link SplitAttributes} is an {@link ExpandContainersSplitType} and it
+ * should show a draggable handle that allows the user to drag and restore it into a split.
+ * This state is a result of user dragging the divider to fully expand the secondary container.
+ */
+ static boolean isDraggableExpandType(@NonNull SplitAttributes splitAttributes) {
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+ return splitAttributes.getSplitType() instanceof ExpandContainersSplitType
+ && dividerAttributes != null
+ && dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE;
+
+ }
}
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 b9b86f0..0f04321 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -21,6 +21,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -115,6 +116,11 @@
static final boolean ENABLE_SHELL_TRANSITIONS =
SystemProperties.getBoolean("persist.wm.debug.shell_transit", true);
+ // TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
+ // association. It's not set in WM Extensions nor Wm Jetpack library currently.
+ private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
+ "androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
+
@VisibleForTesting
@GuardedBy("mLock")
final SplitPresenter mPresenter;
@@ -1405,9 +1411,27 @@
launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
}
+ @GuardedBy("mLock")
+ private void onActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
+ // Checks if there's any finishing activity in paused state associate with an overlay
+ // container. #OnActivityPostDestroyed is a very late signal, which is called after activity
+ // is not visible and the next activity shows on screen.
+ if (!activity.isFinishing()) {
+ // onPaused is triggered without finishing. Early return.
+ return;
+ }
+ // Check if we should dismiss the overlay container with this finishing activity.
+ final IBinder activityToken = activity.getActivityToken();
+ for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+ mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken);
+ }
+ updateCallbackIfNecessary();
+ }
+
@VisibleForTesting
@GuardedBy("mLock")
- void onActivityDestroyed(@NonNull Activity activity) {
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct, @NonNull Activity activity) {
if (!activity.isFinishing()) {
// onDestroyed is triggered without finishing. This happens when the activity is
// relaunched. In this case, we don't want to cleanup the record.
@@ -1417,7 +1441,7 @@
// organizer.
final IBinder activityToken = activity.getActivityToken();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- mTaskContainers.valueAt(i).onActivityDestroyed(activityToken);
+ mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken);
}
// We didn't trigger the callback if there were any pending appeared activities, so check
// again after the pending is removed.
@@ -1597,7 +1621,8 @@
@Nullable Activity launchingActivity) {
return createEmptyContainer(wct, intent, taskId,
new ActivityStackAttributes.Builder().build(), launchingActivity,
- null /* overlayTag */, null /* launchOptions */);
+ null /* overlayTag */, null /* launchOptions */,
+ false /* shouldAssociateWithLaunchingActivity */);
}
/**
@@ -1612,7 +1637,7 @@
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@NonNull ActivityStackAttributes activityStackAttributes,
@Nullable Activity launchingActivity, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1630,7 +1655,7 @@
}
final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
- launchOptions);
+ launchOptions, associateLaunchingActivity);
final IBinder taskFragmentToken = container.getTaskFragmentToken();
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
@@ -1722,7 +1747,7 @@
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
@GuardedBy("mLock")
@@ -1730,7 +1755,7 @@
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
@GuardedBy("mLock")
@@ -1739,7 +1764,7 @@
@NonNull TaskFragmentContainer pairedPrimaryContainer) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
- null /* launchOptions */);
+ null /* launchOptions */, false /* associateLaunchingActivity */);
}
/**
@@ -1751,19 +1776,21 @@
* @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed.
* @param taskId parent Task of the new TaskFragment.
- * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * @param pairedContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
* @param overlayTag The tag for the new created overlay container. It must be
* needed if {@code isOverlay} is {@code true}. Otherwise,
* it should be {@code null}.
* @param launchOptions The launch options bundle to create a container. Must be
* specified for overlay container.
+ * @param associateLaunchingActivity {@code true} to indicate this overlay container
+ * should associate with launching activity.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable TaskFragmentContainer pairedContainer, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions, boolean associateLaunchingActivity) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1773,8 +1800,8 @@
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
- launchOptions);
+ pendingAppearedIntent, taskContainer, this, pairedContainer, overlayTag,
+ launchOptions, associateLaunchingActivity ? activityInTask : null);
return container;
}
@@ -2141,6 +2168,10 @@
return false;
}
+ if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) {
+ return false;
+ }
+
final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent());
@@ -2236,6 +2267,9 @@
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false;
}
+ if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) {
+ return false;
+ }
mTransactionManager.getCurrentTransactionRecord()
.setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
@@ -2617,6 +2651,8 @@
final List<TaskFragmentContainer> overlayContainers =
getAllOverlayTaskFragmentContainers();
final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
+ final boolean associateLaunchingActivity = options
+ .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true);
// If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
// specified by Intent, expand the overlay container to fill the parent task instead.
@@ -2646,6 +2682,11 @@
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId != overlayContainer.getTaskId()) {
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different task ID:" + overlayContainer.getTaskId() + ". "
+ + "The new associated activity is " + launchActivity);
// If there's an overlay container with same tag in a different task,
// dismiss the overlay container since the tag must be unique per process.
mPresenter.cleanupContainer(wct, overlayContainer,
@@ -2653,19 +2694,46 @@
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
- mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
- getMinDimensions(intent));
- // We can just return the updated overlay container and don't need to
- // check other condition since we only have one OverlayCreateParams, and
- // if the tag and task are matched, it's impossible to match another task
- // or tag since tags and tasks are all unique.
- return overlayContainer;
+ if (associateLaunchingActivity && !launchActivity.getActivityToken()
+ .equals(overlayContainer.getAssociatedActivityToken())) {
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different associated launching activity. The new associated"
+ + " activity is " + launchActivity);
+ // The associated activity must be the same, or it will be dismissed.
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ } else if (!associateLaunchingActivity
+ && overlayContainer.isAssociatedWithActivity()) {
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed because"
+ + " there's an existing overlay container with the same tag but"
+ + " different associated launching activity. The overlay container"
+ + " doesn't associate with any activity.");
+ // Dismiss the overlay container since it has been associated with an
+ // activity.
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ } else {
+ // Just update the overlay container if
+ // - should associate with an activity and associated activity matches
+ // - should not associate with an activity and the overlay container
+ // don't have an associated activity
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
+ // We can just return the updated overlay container and don't need to
+ // check other condition since we only have one OverlayCreateParams, and
+ // if the tag and task are matched, it's impossible to match another task
+ // or tag since tags and tasks are all unique.
+ return overlayContainer;
+ }
}
}
}
// Launch the overlay container to the task with taskId.
return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
- options);
+ options, associateLaunchingActivity);
}
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2754,6 +2822,23 @@
}
@Override
+ public void onActivityPostPaused(@NonNull Activity activity) {
+ if (activity.isChild()) {
+ // Skip Activity that is child of another Activity (ActivityGroup) because it's
+ // window will just be a child of the parent Activity window.
+ return;
+ }
+ synchronized (mLock) {
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+ SplitController.this.onActivityPaused(
+ transactionRecord.getTransaction(), activity);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+ }
+ }
+
+ @Override
public void onActivityPostDestroyed(@NonNull Activity activity) {
if (activity.isChild()) {
// Skip Activity that is child of another Activity (ActivityGroup) because it's
@@ -2761,7 +2846,12 @@
return;
}
synchronized (mLock) {
- SplitController.this.onActivityDestroyed(activity);
+ final TransactionRecord transactionRecord = mTransactionManager
+ .startNewTransaction();
+ transactionRecord.setOriginType(TRANSIT_CLOSE);
+ SplitController.this.onActivityDestroyed(
+ transactionRecord.getTransaction(), activity);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
}
@@ -3111,7 +3201,12 @@
if (taskContainer != null) {
final DividerPresenter dividerPresenter =
mDividerPresenters.get(taskContainer.getTaskId());
- taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
+ final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
+ taskContainer.updateTopSplitContainerForDivider(
+ dividerPresenter, containersToFinish);
+ for (final TaskFragmentContainer container : containersToFinish) {
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ }
updateContainersInTask(wct, taskContainer);
}
action.accept(wct);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0d31266..b56f671 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -565,7 +565,7 @@
@Override
void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder primary,
- @Nullable IBinder secondary) {
+ @Nullable IBinder secondary) {
final TaskFragmentContainer container = mController.getContainer(primary);
if (container == null) {
throw new IllegalStateException("setCompanionTaskFragment on TaskFragment that is"
@@ -590,7 +590,12 @@
final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
taskBounds);
final boolean isFillParent = relativeBounds.isEmpty();
- final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ // Note that we only set isolated navigation for overlay container without activity
+ // association. Activity will be launched to an expanded container on top of the overlay
+ // if the overlay is associated with an activity. Thus, an overlay with activity association
+ // will never be isolated navigated.
+ final boolean isIsolatedNavigated = container.isOverlay()
+ && !container.isAssociatedWithActivity() && !isFillParent;
final boolean dimOnTask = !isFillParent
&& attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK
&& Flags.fullscreenDimFlag();
@@ -716,6 +721,12 @@
return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
}
+ static boolean shouldShowPlaceholderWhenExpanded(@NonNull SplitAttributes splitAttributes) {
+ // The placeholder should be kept if the expand split type is a result of user dragging
+ // the divider.
+ return SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+ }
+
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
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 a215bdf..3dd96c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -22,6 +22,9 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.inMultiWindowMode;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_PRIMARY;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_SECONDARY;
+
import android.app.Activity;
import android.app.ActivityClient;
import android.app.WindowConfiguration;
@@ -40,6 +43,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
import java.util.ArrayList;
import java.util.List;
@@ -89,6 +95,25 @@
final Set<IBinder> mFinishedContainer = new ArraySet<>();
/**
+ * The {@link RatioSplitType} that will be applied to newly added containers. This is to ensure
+ * the required UX that, after user dragging the divider, the split ratio is persistent after
+ * launching a new activity into a new TaskFragment in the same Task.
+ */
+ private RatioSplitType mOverrideSplitType;
+
+ /**
+ * If {@code true}, suppress the placeholder rules in the {@link TaskContainer}.
+ * <p>
+ * This is used in case the user drags the divider to fully expand the primary container and
+ * dismiss the secondary container while a {@link SplitPlaceholderRule} is used. Without this
+ * flag, after dismissing the secondary container, a placeholder will be launched again.
+ * <p>
+ * This flag is set true when the primary container is fully expanded and cleared when a new
+ * split is added to the {@link TaskContainer}.
+ */
+ private boolean mPlaceholderRuleSuppressed;
+
+ /**
* The {@link TaskContainer} constructor
*
* @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
@@ -211,10 +236,19 @@
return mContainers.isEmpty() && mFinishedContainer.isEmpty();
}
- /** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull IBinder activityToken) {
+ /** Called when the activity {@link Activity#isFinishing()} and paused. */
+ void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) {
- container.onActivityDestroyed(activityToken);
+ container.onFinishingActivityPaused(wct, activityToken);
+ }
+ }
+
+ /** Called when the activity is destroyed. */
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ for (TaskFragmentContainer container : mContainers) {
+ container.onActivityDestroyed(wct, activityToken);
}
}
@@ -313,6 +347,11 @@
}
void addSplitContainer(@NonNull SplitContainer splitContainer) {
+ // Reset the placeholder rule suppression when a new split container is added.
+ mPlaceholderRuleSuppressed = false;
+
+ applyOverrideSplitTypeIfNeeded(splitContainer);
+
if (splitContainer instanceof SplitPinContainer) {
mSplitPinContainer = (SplitPinContainer) splitContainer;
mSplitContainers.add(splitContainer);
@@ -327,6 +366,39 @@
}
}
+ boolean isPlaceholderRuleSuppressed() {
+ return mPlaceholderRuleSuppressed;
+ }
+
+ // If there is an override SplitType due to user dragging the divider, the split ratio should
+ // be applied to newly added SplitContainers.
+ private void applyOverrideSplitTypeIfNeeded(@NonNull SplitContainer splitContainer) {
+ if (mOverrideSplitType == null) {
+ return;
+ }
+ final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+ final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+ if (!(splitAttributes.getSplitType() instanceof RatioSplitType)) {
+ // Skip if the original split type is not a ratio type.
+ return;
+ }
+ if (dividerAttributes == null
+ || dividerAttributes.getDividerType() != DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+ // Skip if the split does not have a draggable divider.
+ return;
+ }
+ updateDefaultSplitAttributes(splitContainer, mOverrideSplitType);
+ }
+
+ private static void updateDefaultSplitAttributes(
+ @NonNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType) {
+ splitContainer.updateDefaultSplitAttributes(
+ new SplitAttributes.Builder(splitContainer.getDefaultSplitAttributes())
+ .setSplitType(overrideSplitType)
+ .build()
+ );
+ }
+
void removeSplitContainers(@NonNull List<SplitContainer> containers) {
mSplitContainers.removeAll(containers);
}
@@ -398,18 +470,47 @@
return mContainers;
}
- void updateTopSplitContainerForDivider(@NonNull DividerPresenter dividerPresenter) {
+ void updateTopSplitContainerForDivider(
+ @NonNull DividerPresenter dividerPresenter,
+ @NonNull List<TaskFragmentContainer> outContainersToFinish) {
final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer();
if (topSplitContainer == null) {
return;
}
-
+ final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
- topSplitContainer.updateDefaultSplitAttributes(
- new SplitAttributes.Builder(topSplitContainer.getDefaultSplitAttributes())
- .setSplitType(new SplitAttributes.SplitType.RatioSplitType(newRatio))
- .build()
- );
+
+ // If the primary container is fully expanded, we should finish all the associated
+ // secondary containers.
+ if (newRatio == RATIO_EXPANDED_PRIMARY) {
+ for (final SplitContainer splitContainer : mSplitContainers) {
+ if (primaryContainer == splitContainer.getPrimaryContainer()) {
+ outContainersToFinish.add(splitContainer.getSecondaryContainer());
+ }
+ }
+
+ // Temporarily suppress the placeholder rule in the TaskContainer. This will be restored
+ // if a new split is added into the TaskContainer.
+ mPlaceholderRuleSuppressed = true;
+
+ mOverrideSplitType = null;
+ return;
+ }
+
+ final SplitType newSplitType;
+ if (newRatio == RATIO_EXPANDED_SECONDARY) {
+ newSplitType = new ExpandContainersSplitType();
+ // We do not want to apply ExpandContainersSplitType to new split containers.
+ mOverrideSplitType = null;
+ } else {
+ // We save the override RatioSplitType and apply to new split containers.
+ newSplitType = mOverrideSplitType = new RatioSplitType(newRatio);
+ }
+ for (final SplitContainer splitContainer : mSplitContainers) {
+ if (primaryContainer == splitContainer.getPrimaryContainer()) {
+ updateDefaultSplitAttributes(splitContainer, newSplitType);
+ }
+ }
}
@Nullable
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e20a3e0..5dbb016 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -113,6 +113,18 @@
@NonNull
private final Bundle mLaunchOptions = new Bundle();
+ /**
+ * The associated {@link Activity#getActivityToken()} of the overlay container.
+ * Must be {@code null} for non-overlay container.
+ * <p>
+ * If an overlay container is associated with an activity, this overlay container will be
+ * dismissed when the associated activity is destroyed. If the overlay container is visible,
+ * activity will be launched on top of the overlay container and expanded to fill the parent
+ * container.
+ */
+ @Nullable
+ private final IBinder mAssociatedActivityToken;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -178,7 +190,7 @@
/**
* @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
- * TaskFragmentContainer, String, Bundle)
+ * TaskFragmentContainer, String, Bundle, Activity)
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent,
@@ -187,7 +199,7 @@
@Nullable TaskFragmentContainer pairedPrimaryContainer) {
this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
controller, pairedPrimaryContainer, null /* overlayTag */,
- null /* launchOptions */);
+ null /* launchOptions */, null /* associatedActivity */);
}
/**
@@ -197,12 +209,14 @@
* @param overlayTag Sets to indicate this taskFragment is an overlay container
* @param launchOptions The launch options to create this container. Must not be
* {@code null} for an overlay container
+ * @param associatedActivity the associated activity of the overlay container. Must be
+ * {@code null} for a non-overlay container.
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
@Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
- @Nullable Bundle launchOptions) {
+ @Nullable Bundle launchOptions, @Nullable Activity associatedActivity) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -214,7 +228,13 @@
mOverlayTag = overlayTag;
if (overlayTag != null) {
Objects.requireNonNull(launchOptions);
+ } else if (associatedActivity != null) {
+ throw new IllegalArgumentException("Associated activity must be null for "
+ + "non-overlay activity.");
}
+ mAssociatedActivityToken = associatedActivity != null
+ ? associatedActivity.getActivityToken() : null;
+
if (launchOptions != null) {
mLaunchOptions.putAll(launchOptions);
}
@@ -420,14 +440,38 @@
}
}
+ /** Called when the activity {@link Activity#isFinishing()} and paused. */
+ void onFinishingActivityPaused(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ finishSelfWithActivityIfNeeded(wct, activityToken);
+ }
+
/** Called when the activity is destroyed. */
- void onActivityDestroyed(@NonNull IBinder activityToken) {
+ void onActivityDestroyed(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
removePendingAppearedActivity(activityToken);
if (mInfo != null) {
// Remove the activity now because there can be a delay before the server callback.
mInfo.getActivities().remove(activityToken);
}
mActivitiesToFinishOnExit.remove(activityToken);
+ finishSelfWithActivityIfNeeded(wct, activityToken);
+ }
+
+ @VisibleForTesting
+ void finishSelfWithActivityIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder activityToken) {
+ if (mIsFinished) {
+ return;
+ }
+ // Early return if this container is not an overlay with activity association.
+ if (!isOverlay() || !isAssociatedWithActivity()) {
+ return;
+ }
+ if (mAssociatedActivityToken == activityToken) {
+ // If the associated activity is destroyed, also finish this overlay container.
+ mController.mPresenter.cleanupContainer(wct, this, false /* shouldFinishDependent */);
+ }
}
@Nullable
@@ -961,6 +1005,24 @@
return mLaunchOptions;
}
+ /**
+ * Returns the associated Activity token of this overlay container. It must be {@code null}
+ * for non-overlay container.
+ * <p>
+ * If an overlay container is associated with an activity, this overlay container will be
+ * dismissed when the associated activity is destroyed. If the overlay container is visible,
+ * activity will be launched on top of the overlay container and expanded to fill the parent
+ * container.
+ */
+ @Nullable
+ IBinder getAssociatedActivityToken() {
+ return mAssociatedActivityToken;
+ }
+
+ boolean isAssociatedWithActivity() {
+ return mAssociatedActivityToken != null;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -980,6 +1042,7 @@
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ " overlayTag=" + mOverlayTag
+ + " associatedActivity" + mAssociatedActivityToken
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 47d01da..de0171d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -144,10 +144,12 @@
new Configuration(),
DEFAULT_DIVIDER_ATTRIBUTES,
mSurfaceControl,
- getInitialDividerPosition(mSplitContainer),
+ getInitialDividerPosition(
+ mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */),
true /* isVerticalSplit */,
false /* isReversedLayout */,
- Display.DEFAULT_DISPLAY);
+ Display.DEFAULT_DISPLAY,
+ false /* isDraggableExpandType */);
mDividerPresenter = new DividerPresenter(
MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -348,7 +350,8 @@
dividerWidthPx,
dividerAttributes,
true /* isVerticalSplit */,
- false /* isReversedLayout */));
+ 0 /* minPosition */,
+ 900 /* maxPosition */));
// Top-to-bottom split
when(event.getRawY()).thenReturn(1000f); // Touch event is in display space
@@ -361,7 +364,8 @@
dividerWidthPx,
dividerAttributes,
false /* isVerticalSplit */,
- false /* isReversedLayout */));
+ 0 /* minPosition */,
+ 900 /* maxPosition */));
}
@Test
@@ -453,7 +457,6 @@
final Rect primaryBounds = new Rect(0, 0, 500, 2000);
final Rect secondaryBounds = new Rect(600, 0, 1100, 2000);
final int dividerWidthPx = 100;
- final int dividerPosition = 300;
final TaskFragmentContainer mockPrimaryContainer =
createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -462,6 +465,8 @@
when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+ // Test the normal case
+ int dividerPosition = 300;
assertEquals(
0.3f, // Primary is 300px after dragging.
DividerPresenter.calculateNewSplitRatio(
@@ -470,7 +475,43 @@
taskBounds,
dividerWidthPx,
true /* isVerticalSplit */,
- false /* isReversedLayout */),
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is allowed and divider is dragged to the edge
+ dividerPosition = 0;
+ assertEquals(
+ DividerPresenter.RATIO_EXPANDED_SECONDARY,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ 0.2f, // Adjusted to the minPosition 200
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ true /* isVerticalSplit */,
+ false /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
0.0001 /* delta */);
}
@@ -482,7 +523,6 @@
final Rect primaryBounds = new Rect(0, 0, 2000, 1100);
final Rect secondaryBounds = new Rect(0, 0, 2000, 500);
final int dividerWidthPx = 100;
- final int dividerPosition = 300;
final TaskFragmentContainer mockPrimaryContainer =
createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -491,6 +531,8 @@
when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
+ // Test the normal case
+ int dividerPosition = 300;
assertEquals(
// After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
0.7f,
@@ -500,7 +542,46 @@
taskBounds,
dividerWidthPx,
false /* isVerticalSplit */,
- true /* isReversedLayout */),
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ // The primary (bottom) container is expanded
+ DividerPresenter.RATIO_EXPANDED_PRIMARY,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ false /* isVerticalSplit */,
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ true /* isDraggingToFullscreenAllowed */),
+ 0.0001 /* delta */);
+
+ // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+ // edge.
+ dividerPosition = 0;
+ assertEquals(
+ // Adjusted to minPosition 200, so the primary (bottom) container is 800.
+ 0.8f,
+ DividerPresenter.calculateNewSplitRatio(
+ mSplitContainer,
+ dividerPosition,
+ taskBounds,
+ dividerWidthPx,
+ false /* isVerticalSplit */,
+ true /* isReversedLayout */,
+ 200 /* minPosition */,
+ 1000 /* maxPosition */,
+ false /* isDraggingToFullscreenAllowed */),
0.0001 /* delta */);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 28fbadb..dcdbe59 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -39,6 +39,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -227,14 +228,15 @@
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() {
createExistingOverlayContainers();
final Rect bounds = new Rect(0, 0, 100, 100);
mSplitController.setActivityStackAttributesCalculator(params ->
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- "test1");
+ mOverlayContainer1.getOverlayTag(),
+ mOverlayContainer1.getTopNonFinishingActivity());
assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ " is launched with the same tag and task")
@@ -247,6 +249,22 @@
}
@Test
+ public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ mSplitController.setActivityStackAttributesCalculator(params ->
+ new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ mOverlayContainer1.getOverlayTag(), mActivity);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is associated with different launching activity")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
createExistingOverlayContainers();
@@ -294,7 +312,7 @@
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test");
- setupTaskFragmentInfo(overlayContainer, mActivity);
+ setupTaskFragmentInfo(overlayContainer, mActivity, true /* isVisible */);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
@@ -510,8 +528,9 @@
}
@Test
- public void testApplyActivityStackAttributesForOverlayContainer() {
- final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ public void testApplyActivityStackAttributesForOverlayContainerAssociatedWithActivity() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID,
+ TEST_TAG, true /* associatedWithLaunchingActivity */);
final IBinder token = container.getTaskFragmentToken();
final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
.setRelativeBounds(new Rect(0, 0, 200, 200))
@@ -527,7 +546,35 @@
WINDOWING_MODE_MULTI_WINDOW);
verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
TaskFragmentAnimationParams.DEFAULT);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ // Set isolated navigation to false if the overlay container is associated with
+ // the launching activity.
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainerWithoutAssociatedActivity() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG,
+ false /* associatedWithLaunchingActivity */);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container,
+ attributes, null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction,
+ container, WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ // Set isolated navigation to false if the overlay container is associated with
+ // the launching activity.
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
+ container, true);
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
}
@@ -573,16 +620,65 @@
verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
}
+ @Test
+ public void testFinishSelfWithActivityIfNeeded() {
+ TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ container.finishSelfWithActivityIfNeeded(mTransaction, mActivity.getActivityToken());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+ TaskFragmentContainer overlayWithoutAssociation = createTestOverlayContainer(TASK_ID,
+ "test", false /* associateLaunchingActivity */);
+
+ overlayWithoutAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+ mActivity.getActivityToken());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .contains(overlayWithoutAssociation);
+
+ TaskFragmentContainer overlayWithAssociation =
+ createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ overlayWithAssociation.setInfo(mTransaction, createMockTaskFragmentInfo(
+ overlayWithAssociation, mActivity, true /* isVisible */));
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .contains(overlayWithAssociation);
+ clearInvocations(mSplitPresenter);
+
+ overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction, new Binder());
+
+ verify(mSplitPresenter, never()).cleanupContainer(any(), any(), anyBoolean());
+
+ overlayWithAssociation.finishSelfWithActivityIfNeeded(mTransaction,
+ mActivity.getActivityToken());
+
+ verify(mSplitPresenter).cleanupContainer(mTransaction, overlayWithAssociation, false);
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .doesNotContain(overlayWithAssociation);
+ }
+
/**
- * A simplified version of {@link SplitController.ActivityStartMonitor
- * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
*/
@Nullable
private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
final Bundle launchOptions = new Bundle();
launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return createOrUpdateOverlayTaskFragmentIfNeeded(tag, mActivity);
+ }
+
+ /**
+ * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull String tag, @NonNull Activity activity) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
- launchOptions, mIntent, mActivity);
+ launchOptions, mIntent, activity);
}
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -590,23 +686,34 @@
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
final TaskFragmentContainer container = mSplitController.newContainer(activity,
activity.getTaskId());
- setupTaskFragmentInfo(container, activity);
+ setupTaskFragmentInfo(container, activity, false /* isVisible */);
return container;
}
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ return createTestOverlayContainer(taskId, tag,
+ true /* associateLaunchingActivity */);
+ }
+
+ // TODO(b/243518738): add more test coverage on overlay container without activity association
+ // once we have use cases.
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
+ boolean associateLaunchingActivity) {
Activity activity = createMockActivity();
TaskFragmentContainer overlayContainer = mSplitController.newContainer(
null /* pendingAppearedActivity */, mIntent, activity, taskId,
- null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
- setupTaskFragmentInfo(overlayContainer, activity);
+ null /* pairedPrimaryContainer */, tag, Bundle.EMPTY,
+ associateLaunchingActivity);
+ setupTaskFragmentInfo(overlayContainer, activity, false /* isVisible */);
return overlayContainer;
}
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
- @NonNull Activity activity) {
- final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ @NonNull Activity activity,
+ boolean isVisible) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible);
container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index c246a19..3441c2b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -227,13 +227,13 @@
// When the activity is not finishing, do not clear the record.
doReturn(false).when(mActivity).isFinishing();
- mSplitController.onActivityDestroyed(mActivity);
+ mSplitController.onActivityDestroyed(mTransaction, mActivity);
assertTrue(tf.hasActivity(mActivity.getActivityToken()));
// Clear the record when the activity is finishing and destroyed.
doReturn(true).when(mActivity).isFinishing();
- mSplitController.onActivityDestroyed(mActivity);
+ mSplitController.onActivityDestroyed(mTransaction, mActivity);
assertFalse(tf.hasActivity(mActivity.getActivityToken()));
}
@@ -612,7 +612,7 @@
assertFalse(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
}
@Test
@@ -775,7 +775,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -818,7 +818,7 @@
assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
- anyString(), any());
+ anyString(), any(), anyBoolean());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index cc00a49..0af4179 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -402,7 +402,7 @@
assertTrue(container.hasActivity(mActivity.getActivityToken()));
- taskContainer.onActivityDestroyed(mActivity.getActivityToken());
+ taskContainer.onActivityDestroyed(mTransaction, mActivity.getActivityToken());
// It should not contain the destroyed Activity.
assertFalse(container.hasActivity(mActivity.getActivityToken()));
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 4ee2c1a..c2c90c8 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -515,8 +515,20 @@
<!-- The size of the icon shown in the resize veil. -->
<dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>
+ <!-- The with of the border around the app task for edge resizing, when
+ enable_windowing_edge_drag_resize is enabled. -->
+ <dimen name="desktop_mode_edge_handle">12dp</dimen>
+
+ <!-- The original width of the border around the app task for edge resizing, when
+ enable_windowing_edge_drag_resize is disabled. -->
<dimen name="freeform_resize_handle">15dp</dimen>
+ <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
+ appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
+ <dimen name="desktop_mode_corner_resize_large">48dp</dimen>
+
+ <!-- The original size of the corner region for darg resizing, when
+ enable_windowing_edge_drag_resize is disabled. -->
<dimen name="freeform_resize_corner">44dp</dimen>
<!-- The width of the area at the sides of the screen where a freeform task will transition to
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index a32b435..4988a94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,6 +28,7 @@
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.Cuj.CujType;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
@@ -108,7 +109,8 @@
}
}
- private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ @VisibleForTesting
+ boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
return apps.length > 0 && mCujType != NO_CUJ;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index b933e5d..ff00a7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -241,6 +241,7 @@
mainChoreographer,
taskOrganizer,
displayController,
+ rootTaskDisplayAreaOrganizer,
syncQueue,
transitions);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 58942ec..487bbfb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -588,7 +588,7 @@
if (taskBoundsBeforeMaximize != null) {
destinationBounds.set(taskBoundsBeforeMaximize)
} else {
- getDefaultDesktopTaskBounds(displayLayout, destinationBounds)
+ destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout))
}
} else {
// Save current bounds so that task can be restored back to original bounds if necessary
@@ -624,18 +624,14 @@
}
}
- private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout, outBounds: Rect) {
+ private fun getDefaultDesktopTaskBounds(displayLayout: DisplayLayout): Rect {
// TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
- // Update width and height with default desktop mode values
- val desiredWidth = screenBounds.width().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- val desiredHeight = screenBounds.height().times(DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
- outBounds.set(0, 0, desiredWidth, desiredHeight)
- // Center the task in screen bounds
- outBounds.offset(
- screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY()
- )
+ val desiredWidth = (displayLayout.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val desiredHeight = (displayLayout.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE).toInt()
+ val heightOffset = (displayLayout.height() - desiredHeight) / 2
+ val widthOffset = (displayLayout.width() - desiredWidth) / 2
+ return Rect(widthOffset, heightOffset,
+ desiredWidth + widthOffset, desiredHeight + heightOffset)
}
private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
@@ -889,8 +885,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
- val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) {
// Display windowing is freeform, set to undefined and inherit it
WINDOWING_MODE_UNDEFINED
} else {
@@ -907,8 +904,9 @@
wct: WindowContainerTransaction,
taskInfo: RunningTaskInfo
) {
- val displayWindowingMode = taskInfo.configuration.windowConfiguration.displayWindowingMode
- val targetWindowingMode = if (displayWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!!
+ val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
+ val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FULLSCREEN) {
// Display windowing is fullscreen, set to undefined and inherit it
WINDOWING_MODE_UNDEFINED
} else {
@@ -1090,17 +1088,14 @@
* @param taskInfo the task being dragged.
* @param y height of drag, to be checked against status bar height.
*/
- fun onDragPositioningEndThroughStatusBar(
- inputCoordinates: PointF,
- taskInfo: RunningTaskInfo,
- freeformBounds: Rect
- ) {
+ fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) {
val indicator = visualIndicator ?: return
val indicatorType = indicator
.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
when (indicatorType) {
DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
- finalizeDragToDesktop(taskInfo, freeformBounds)
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
+ finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout))
}
DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index e8f58fe..62d195e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -37,4 +37,9 @@
* Called when a running task vanishes.
*/
void onRunningTaskVanished(in RunningTaskInfo taskInfo);
+
+ /**
+ * Called when a running task changes.
+ */
+ void onRunningTaskChanged(in RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9fcfac..0c99aed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
@@ -26,7 +27,6 @@
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.PendingIntent;
-import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -86,7 +86,7 @@
private final ActivityTaskManager mActivityTaskManager;
private RecentsTransitionHandler mTransitionHandler = null;
private IRecentTasksListener mListener;
- private final boolean mIsDesktopMode;
+ private final boolean mPcFeatureEnabled;
// Mapping of split task ids, mappings are symmetrical (ie. if t1 is the taskid of a task in a
// pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
@@ -133,7 +133,7 @@
mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
mActivityTaskManager = activityTaskManager;
- mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+ mPcFeatureEnabled = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
@@ -252,8 +252,10 @@
notifyRunningTaskVanished(taskInfo);
}
- public void onTaskWindowingModeChanged(TaskInfo taskInfo) {
+ /** Notify listeners that the windowing mode of the given Task was updated. */
+ public void onTaskWindowingModeChanged(ActivityManager.RunningTaskInfo taskInfo) {
notifyRecentTasksChanged();
+ notifyRunningTaskChanged(taskInfo);
}
@Override
@@ -278,7 +280,9 @@
* Notify the running task listener that a task appeared on desktop environment.
*/
private void notifyRunningTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
- if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
return;
}
try {
@@ -292,7 +296,9 @@
* Notify the running task listener that a task was removed on desktop environment.
*/
private void notifyRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (mListener == null || !mIsDesktopMode || taskInfo.realActivity == null) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
return;
}
try {
@@ -302,6 +308,27 @@
}
}
+ /**
+ * Notify the running task listener that a task was changed on desktop environment.
+ */
+ private void notifyRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mListener == null
+ || !shouldEnableRunningTasksForDesktopMode()
+ || taskInfo.realActivity == null) {
+ return;
+ }
+ try {
+ mListener.onRunningTaskChanged(taskInfo);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call onRunningTaskChanged", e);
+ }
+ }
+
+ private boolean shouldEnableRunningTasksForDesktopMode() {
+ return mPcFeatureEnabled
+ || (DesktopModeStatus.isEnabled() && enableDesktopWindowingTaskbarRunningApps());
+ }
+
@VisibleForTesting
void registerRecentTasksListener(IRecentTasksListener listener) {
mListener = listener;
@@ -476,6 +503,11 @@
public void onRunningTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
mListener.call(l -> l.onRunningTaskVanished(taskInfo));
}
+
+ @Override
+ public void onRunningTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mListener.call(l -> l.onRunningTaskChanged(taskInfo));
+ }
};
public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index c59a1b4..87dc391 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -19,23 +19,30 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
+import android.provider.Settings;
import android.util.SparseArray;
import android.view.Choreographer;
+import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -53,6 +60,7 @@
private final Handler mMainHandler;
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Transitions mTransitions;
private TaskOperations mTaskOperations;
@@ -65,6 +73,7 @@
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
SyncTransactionQueue syncQueue,
Transitions transitions) {
mContext = context;
@@ -72,6 +81,7 @@
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSyncQueue = syncQueue;
mTransitions = transitions;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -158,10 +168,33 @@
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
- return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
- && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
- == WINDOWING_MODE_FREEFORM);
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ return true;
+ }
+ if (taskInfo.getActivityType() != ACTIVITY_TYPE_STANDARD) {
+ return false;
+ }
+ final DisplayAreaInfo rootDisplayAreaInfo =
+ mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+ if (rootDisplayAreaInfo != null) {
+ return rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+
+ // It is possible that the rootDisplayAreaInfo is null when a task appears soon enough after
+ // a new display shows up, because TDA may appear after task appears in WM shell. Instead of
+ // fixing the synchronization issues, let's use other signals to "guess" the answer. It is
+ // OK in this context because no other captions other than the legacy developer option
+ // freeform and Kingyo/CF PC may use this class. WM shell should have full control over the
+ // condition where captions should show up in all new cases such as desktop mode, for which
+ // we should use different window decor view models. Ultimately Kingyo/CF PC may need to
+ // spin up their own window decor view model when they start to care about multiple
+ // displays.
+ if (isPc()) {
+ return true;
+ }
+ return taskInfo.displayId != Display.DEFAULT_DISPLAY
+ && forcesDesktopModeOnExternalDisplays();
}
private void createWindowDecoration(
@@ -231,7 +264,10 @@
mTaskOperations.minimizeTask(mTaskToken);
} else if (id == R.id.maximize_window) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- mTaskOperations.maximizeTask(taskInfo);
+ final DisplayAreaInfo rootDisplayAreaInfo =
+ mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId);
+ mTaskOperations.maximizeTask(taskInfo,
+ rootDisplayAreaInfo.configuration.windowConfiguration.getWindowingMode());
}
}
@@ -305,4 +341,17 @@
return true;
}
}
+
+ /**
+ * Returns if this device is a PC.
+ */
+ private boolean isPc() {
+ return mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
+ }
+
+ private boolean forcesDesktopModeOnExternalDisplays() {
+ final ContentResolver resolver = mContext.getContentResolver();
+ return Settings.Global.getInt(resolver,
+ DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
+ }
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index beead6a..43fd32b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -16,17 +16,23 @@
package com.android.wm.shell.windowdecor;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+
import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
+import android.util.Size;
import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
@@ -222,7 +228,6 @@
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
- 0 /* taskCornerRadius */,
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
@@ -234,12 +239,10 @@
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- final int resize_handle = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_handle);
- final int resize_corner = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_corner);
- mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop);
+ final Resources res = mResult.mRootView.getResources();
+ mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
+ new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+ getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a0f9c6b..7649a782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -858,10 +858,7 @@
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
+ new PointF(ev.getRawX(), ev.getRawY()), relevantDecor.mTaskInfo);
mMoveToDesktopAnimator = null;
return;
} else {
@@ -913,23 +910,6 @@
}
}
- /**
- * Gets bounds of a scaled window centered relative to the screen bounds
- * @param scale the amount to scale to relative to the Screen Bounds
- */
- private Rect calculateFreeformBounds(int displayId, float scale) {
- // TODO(b/319819547): Account for app constraints so apps do not become letterboxed
- final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
- final int screenWidth = displayLayout.width();
- final int screenHeight = displayLayout.height();
-
- final float adjustmentPercentage = (1f - scale) / 2;
- return new Rect((int) (screenWidth * adjustmentPercentage),
- (int) (screenHeight * adjustmentPercentage),
- (int) (screenWidth * (adjustmentPercentage + scale)),
- (int) (screenHeight * (adjustmentPercentage + scale)));
- }
-
@Nullable
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 963b130..2bbe530 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -24,6 +24,9 @@
import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -42,6 +45,7 @@
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
+import android.util.Size;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -276,7 +280,6 @@
mHandler,
mChoreographer,
mDisplay.getDisplayId(),
- mRelayoutParams.mCornerRadius,
mDecorationContainerSurface,
mDragPositioningCallback,
mSurfaceControlBuilderSupplier,
@@ -288,15 +291,13 @@
.getScaledTouchSlop();
mDragDetector.setTouchSlop(touchSlop);
- final int resize_handle = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_handle);
- final int resize_corner = mResult.mRootView.getResources()
- .getDimensionPixelSize(R.dimen.freeform_resize_corner);
-
// If either task geometry or position have changed, update this task's
// exclusion region listener
+ final Resources res = mResult.mRootView.getResources();
if (mDragResizeListener.setGeometry(
- mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
+ new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
+ new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
+ getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
|| !mTaskInfo.positionInParent.equals(mPositionInParent)) {
updateExclusionRegion();
}
@@ -427,7 +428,7 @@
return mHandleMenu != null;
}
- boolean shouldResizeListenerHandleEvent(MotionEvent e, Point offset) {
+ boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
index 8ce2d6d..421ffd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallback.java
@@ -23,6 +23,9 @@
* Callback called when receiving drag-resize or drag-move related input events.
*/
public interface DragPositioningCallback {
+ /**
+ * Indicates the direction of resizing. May be combined together to indicate a diagonal drag.
+ */
@IntDef(flag = true, value = {
CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 97eb4a4..9624d46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -30,6 +30,7 @@
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
@@ -39,6 +40,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.util.Size;
import android.view.Choreographer;
import android.view.IWindowSession;
import android.view.InputChannel;
@@ -55,6 +57,7 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@@ -66,40 +69,20 @@
class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener";
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
- private final Context mContext;
- private final Handler mHandler;
- private final Choreographer mChoreographer;
- private final InputManager mInputManager;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
private final int mDisplayId;
private final IBinder mClientToken;
- private final InputTransferToken mInputTransferToken;
private final SurfaceControl mDecorationSurface;
private final InputChannel mInputChannel;
private final TaskResizeInputEventReceiver mInputEventReceiver;
- private final DragPositioningCallback mCallback;
private final SurfaceControl mInputSinkSurface;
private final IBinder mSinkClientToken;
private final InputChannel mSinkInputChannel;
private final DisplayController mDisplayController;
-
- private int mTaskWidth;
- private int mTaskHeight;
- private int mResizeHandleThickness;
- private int mCornerSize;
- private int mTaskCornerRadius;
-
- private Rect mLeftTopCornerBounds;
- private Rect mRightTopCornerBounds;
- private Rect mLeftBottomCornerBounds;
- private Rect mRightBottomCornerBounds;
-
- private int mDragPointerId = -1;
- private DragDetector mDragDetector;
private final Region mTouchRegion = new Region();
DragResizeInputListener(
@@ -107,23 +90,17 @@
Handler handler,
Choreographer choreographer,
int displayId,
- int taskCornerRadius,
SurfaceControl decorationSurface,
DragPositioningCallback callback,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
DisplayController displayController) {
- mInputManager = context.getSystemService(InputManager.class);
- mContext = context;
- mHandler = handler;
- mChoreographer = choreographer;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mDisplayId = displayId;
- mTaskCornerRadius = taskCornerRadius;
mDecorationSurface = decorationSurface;
mDisplayController = displayController;
mClientToken = new Binder();
- mInputTransferToken = new InputTransferToken();
+ final InputTransferToken inputTransferToken = new InputTransferToken();
mInputChannel = new InputChannel();
try {
mWindowSession.grantInputChannel(
@@ -136,18 +113,19 @@
INPUT_FEATURE_SPY,
TYPE_APPLICATION,
null /* windowToken */,
- mInputTransferToken,
+ inputTransferToken,
TAG + " of " + decorationSurface.toString(),
mInputChannel);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
- mInputEventReceiver = new TaskResizeInputEventReceiver(
- mInputChannel, mHandler, mChoreographer);
- mCallback = callback;
- mDragDetector = new DragDetector(mInputEventReceiver);
- mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
+ mInputEventReceiver = new TaskResizeInputEventReceiver(context, mInputChannel, callback,
+ handler, choreographer, () -> {
+ final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
+ return new Size(layout.width(), layout.height());
+ }, this::updateSinkInputChannel);
+ mInputEventReceiver.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop());
mInputSinkSurface = surfaceControlBuilderSupplier.get()
.setName("TaskInputSink of " + decorationSurface)
@@ -171,7 +149,7 @@
INPUT_FEATURE_NO_INPUT_CHANNEL,
TYPE_INPUT_CONSUMER,
null /* windowToken */,
- mInputTransferToken,
+ inputTransferToken,
"TaskInputSink of " + decorationSurface,
mSinkInputChannel);
} catch (RemoteException e) {
@@ -182,86 +160,26 @@
/**
* Updates the geometry (the touch region) of this drag resize handler.
*
- * @param taskWidth The width of the task.
- * @param taskHeight The height of the task.
- * @param resizeHandleThickness The thickness of the resize handle in pixels.
- * @param cornerSize The size of the resize handle centered in each corner.
- * @param touchSlop The distance in pixels user has to drag with touch for it to register as
- * a resize action.
+ * @param incomingGeometry The geometry update to apply for this task's drag resize regions.
+ * @param touchSlop The distance in pixels user has to drag with touch for it to register
+ * as a resize action.
* @return whether the geometry has changed or not
*/
- boolean setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
- int touchSlop) {
- if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
- && mResizeHandleThickness == resizeHandleThickness
- && mCornerSize == cornerSize) {
+ boolean setGeometry(@NonNull DragResizeWindowGeometry incomingGeometry, int touchSlop) {
+ DragResizeWindowGeometry geometry = mInputEventReceiver.getGeometry();
+ if (incomingGeometry.equals(geometry)) {
+ // Geometry hasn't changed size so skip all updates.
return false;
+ } else {
+ geometry = incomingGeometry;
}
-
- mTaskWidth = taskWidth;
- mTaskHeight = taskHeight;
- mResizeHandleThickness = resizeHandleThickness;
- mCornerSize = cornerSize;
- mDragDetector.setTouchSlop(touchSlop);
+ mInputEventReceiver.setTouchSlop(touchSlop);
mTouchRegion.setEmpty();
- final Rect topInputBounds = new Rect(
- -mResizeHandleThickness,
- -mResizeHandleThickness,
- mTaskWidth + mResizeHandleThickness,
- 0);
- mTouchRegion.union(topInputBounds);
-
- final Rect leftInputBounds = new Rect(
- -mResizeHandleThickness,
- 0,
- 0,
- mTaskHeight);
- mTouchRegion.union(leftInputBounds);
-
- final Rect rightInputBounds = new Rect(
- mTaskWidth,
- 0,
- mTaskWidth + mResizeHandleThickness,
- mTaskHeight);
- mTouchRegion.union(rightInputBounds);
-
- final Rect bottomInputBounds = new Rect(
- -mResizeHandleThickness,
- mTaskHeight,
- mTaskWidth + mResizeHandleThickness,
- mTaskHeight + mResizeHandleThickness);
- mTouchRegion.union(bottomInputBounds);
-
- // Set up touch areas in each corner.
- int cornerRadius = mCornerSize / 2;
- mLeftTopCornerBounds = new Rect(
- -cornerRadius,
- -cornerRadius,
- cornerRadius,
- cornerRadius);
- mTouchRegion.union(mLeftTopCornerBounds);
-
- mRightTopCornerBounds = new Rect(
- mTaskWidth - cornerRadius,
- -cornerRadius,
- mTaskWidth + cornerRadius,
- cornerRadius);
- mTouchRegion.union(mRightTopCornerBounds);
-
- mLeftBottomCornerBounds = new Rect(
- -cornerRadius,
- mTaskHeight - cornerRadius,
- cornerRadius,
- mTaskHeight + cornerRadius);
- mTouchRegion.union(mLeftBottomCornerBounds);
-
- mRightBottomCornerBounds = new Rect(
- mTaskWidth - cornerRadius,
- mTaskHeight - cornerRadius,
- mTaskWidth + cornerRadius,
- mTaskHeight + cornerRadius);
- mTouchRegion.union(mRightBottomCornerBounds);
+ // Apply the geometry to the touch region.
+ geometry.union(mTouchRegion);
+ mInputEventReceiver.setGeometry(geometry);
+ mInputEventReceiver.setTouchRegion(mTouchRegion);
try {
mWindowSession.updateInputChannel(
@@ -276,8 +194,9 @@
e.rethrowFromSystemServer();
}
+ final Size taskSize = geometry.getTaskSize();
mSurfaceControlTransactionSupplier.get()
- .setWindowCrop(mInputSinkSurface, mTaskWidth, mTaskHeight)
+ .setWindowCrop(mInputSinkSurface, taskSize.getWidth(), taskSize.getHeight())
.apply();
// The touch region of the TaskInputSink should be the touch region of this
// DragResizeInputHandler minus the task bounds. Pilfering events isn't enough to prevent
@@ -290,21 +209,16 @@
// issue. However, were there touchscreen-only a region out of the task bounds, mouse
// gestures will become no-op in that region, even though the mouse gestures may appear to
// be performed on the input window behind the resize handle.
- mTouchRegion.op(0, 0, mTaskWidth, mTaskHeight, Region.Op.DIFFERENCE);
+ mTouchRegion.op(0, 0, taskSize.getWidth(), taskSize.getHeight(), Region.Op.DIFFERENCE);
updateSinkInputChannel(mTouchRegion);
return true;
}
/**
- * Generate a Region that encapsulates all 4 corner handles
+ * Generate a Region that encapsulates all 4 corner handles and window edges.
*/
- Region getCornersRegion() {
- Region region = new Region();
- region.union(mLeftTopCornerBounds);
- region.union(mLeftBottomCornerBounds);
- region.union(mRightTopCornerBounds);
- region.union(mRightBottomCornerBounds);
- return region;
+ @NonNull Region getCornersRegion() {
+ return mInputEventReceiver.getCornersRegion();
}
private void updateSinkInputChannel(Region region) {
@@ -322,7 +236,7 @@
}
}
- boolean shouldHandleEvent(MotionEvent e, Point offset) {
+ boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
return mInputEventReceiver.shouldHandleEvent(e, offset);
}
@@ -351,19 +265,37 @@
.apply();
}
- private class TaskResizeInputEventReceiver extends InputEventReceiver
- implements DragDetector.MotionEventHandler {
- private final Choreographer mChoreographer;
- private final Runnable mConsumeBatchEventRunnable;
+ private static class TaskResizeInputEventReceiver extends InputEventReceiver implements
+ DragDetector.MotionEventHandler {
+ @NonNull private final Context mContext;
+ private final InputManager mInputManager;
+ @NonNull private final InputChannel mInputChannel;
+ @NonNull private final DragPositioningCallback mCallback;
+ @NonNull private final Choreographer mChoreographer;
+ @NonNull private final Runnable mConsumeBatchEventRunnable;
+ @NonNull private final DragDetector mDragDetector;
+ @NonNull private final Supplier<Size> mDisplayLayoutSizeSupplier;
+ @NonNull private final Consumer<Region> mTouchRegionConsumer;
+ private final Rect mTmpRect = new Rect();
private boolean mConsumeBatchEventScheduled;
+ private DragResizeWindowGeometry mDragResizeWindowGeometry;
+ private Region mTouchRegion;
private boolean mShouldHandleEvents;
private int mLastCursorType = PointerIcon.TYPE_DEFAULT;
private Rect mDragStartTaskBounds;
- private final Rect mTmpRect = new Rect();
+ private int mDragPointerId = -1;
- private TaskResizeInputEventReceiver(
- InputChannel inputChannel, Handler handler, Choreographer choreographer) {
+ private TaskResizeInputEventReceiver(@NonNull Context context,
+ @NonNull InputChannel inputChannel,
+ @NonNull DragPositioningCallback callback, @NonNull Handler handler,
+ @NonNull Choreographer choreographer,
+ @NonNull Supplier<Size> displayLayoutSizeSupplier,
+ @NonNull Consumer<Region> touchRegionConsumer) {
super(inputChannel, handler.getLooper());
+ mContext = context;
+ mInputManager = context.getSystemService(InputManager.class);
+ mInputChannel = inputChannel;
+ mCallback = callback;
mChoreographer = choreographer;
mConsumeBatchEventRunnable = () -> {
@@ -376,6 +308,48 @@
scheduleConsumeBatchEvent();
}
};
+
+ mDragDetector = new DragDetector(this);
+ mDisplayLayoutSizeSupplier = displayLayoutSizeSupplier;
+ mTouchRegionConsumer = touchRegionConsumer;
+ }
+
+ /**
+ * Returns the geometry of the areas to drag resize.
+ */
+ DragResizeWindowGeometry getGeometry() {
+ return mDragResizeWindowGeometry;
+ }
+
+ /**
+ * Updates the geometry of the areas to drag resize.
+ */
+ void setGeometry(@NonNull DragResizeWindowGeometry dragResizeWindowGeometry) {
+ mDragResizeWindowGeometry = dragResizeWindowGeometry;
+ }
+
+ /**
+ * Sets how much slop to allow for touches.
+ */
+ void setTouchSlop(int touchSlop) {
+ mDragDetector.setTouchSlop(touchSlop);
+ }
+
+ /**
+ * Updates the region accepting input for drag resizing the task.
+ */
+ void setTouchRegion(@NonNull Region touchRegion) {
+ mTouchRegion = touchRegion;
+ }
+
+ /**
+ * Returns the union of all regions that can be touched for drag resizing; the corners and
+ * window edges.
+ */
+ @NonNull Region getCornersRegion() {
+ Region region = new Region();
+ mDragResizeWindowGeometry.union(region);
+ return region;
}
@Override
@@ -416,14 +390,15 @@
boolean isTouch = isTouchEvent(e);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
- mShouldHandleEvents = shouldHandleEvent(e, isTouch, new Point() /* offset */);
+ mShouldHandleEvents = mDragResizeWindowGeometry.shouldHandleEvent(e, isTouch,
+ new Point() /* offset */);
if (mShouldHandleEvents) {
mDragPointerId = e.getPointerId(0);
float x = e.getX(0);
float y = e.getY(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
- int ctrlType = calculateCtrlType(isTouch, x, y);
+ int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y);
mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType,
rawX, rawY);
// Increase the input sink region to cover the whole screen; this is to
@@ -455,7 +430,7 @@
// If taskBounds has changed, setGeometry will be called and update the
// sink region. Otherwise, we should revert it here.
if (taskBounds.equals(mDragStartTaskBounds)) {
- updateSinkInputChannel(mTouchRegion);
+ mTouchRegionConsumer.accept(mTouchRegion);
}
}
mShouldHandleEvents = false;
@@ -480,125 +455,20 @@
private void updateInputSinkRegionForDrag(Rect taskBounds) {
mTmpRect.set(taskBounds);
- final DisplayLayout layout = mDisplayController.getDisplayLayout(mDisplayId);
- final Region dragTouchRegion = new Region(-taskBounds.left,
- -taskBounds.top,
- -taskBounds.left + layout.width(),
- -taskBounds.top + layout.height());
+ final Size displayLayoutSize = mDisplayLayoutSizeSupplier.get();
+ final Region dragTouchRegion = new Region(-taskBounds.left, -taskBounds.top,
+ -taskBounds.left + displayLayoutSize.getWidth(),
+ -taskBounds.top + displayLayoutSize.getHeight());
// Remove the localized task bounds from the touch region.
mTmpRect.offsetTo(0, 0);
dragTouchRegion.op(mTmpRect, Region.Op.DIFFERENCE);
- updateSinkInputChannel(dragTouchRegion);
- }
-
- private boolean isInCornerBounds(float xf, float yf) {
- return calculateCornersCtrlType(xf, yf) != 0;
- }
-
- private boolean isInResizeHandleBounds(float x, float y) {
- return calculateResizeHandlesCtrlType(x, y) != 0;
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateCtrlType(boolean isTouch, float x, float y) {
- if (isTouch) {
- return calculateCornersCtrlType(x, y);
- }
- return calculateResizeHandlesCtrlType(x, y);
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateResizeHandlesCtrlType(float x, float y) {
- int ctrlType = 0;
- // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
- // sides will use the bounds specified in setGeometry and not go into task bounds.
- if (x < mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_LEFT;
- }
- if (x > mTaskWidth - mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_RIGHT;
- }
- if (y < mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_TOP;
- }
- if (y > mTaskHeight - mTaskCornerRadius) {
- ctrlType |= CTRL_TYPE_BOTTOM;
- }
- // Check distances from the center if it's in one of four corners.
- if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
- && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
- return checkDistanceFromCenter(ctrlType, x, y);
- }
- // Otherwise, we should make sure we don't resize tasks inside task bounds.
- return (x < 0 || y < 0 || x >= mTaskWidth || y >= mTaskHeight) ? ctrlType : 0;
- }
-
- // If corner input is not within appropriate distance of corner radius, do not use it.
- // If input is not on a corner or is within valid distance, return ctrlType.
- @DragPositioningCallback.CtrlType
- private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType,
- float x, float y) {
- int centerX;
- int centerY;
-
- // Determine center of rounded corner circle; this is simply the corner if radius is 0.
- switch (ctrlType) {
- case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
- centerX = mTaskCornerRadius;
- centerY = mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
- centerX = mTaskCornerRadius;
- centerY = mTaskHeight - mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
- centerX = mTaskWidth - mTaskCornerRadius;
- centerY = mTaskCornerRadius;
- break;
- }
- case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
- centerX = mTaskWidth - mTaskCornerRadius;
- centerY = mTaskHeight - mTaskCornerRadius;
- break;
- }
- default: {
- throw new IllegalArgumentException("ctrlType should be complex, but it's 0x"
- + Integer.toHexString(ctrlType));
- }
- }
- double distanceFromCenter = Math.hypot(x - centerX, y - centerY);
-
- if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
- && distanceFromCenter >= mTaskCornerRadius) {
- return ctrlType;
- }
- return 0;
- }
-
- @DragPositioningCallback.CtrlType
- private int calculateCornersCtrlType(float x, float y) {
- int xi = (int) x;
- int yi = (int) y;
- if (mLeftTopCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
- }
- if (mLeftBottomCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
- }
- if (mRightTopCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
- }
- if (mRightBottomCornerBounds.contains(xi, yi)) {
- return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
- }
- return 0;
+ mTouchRegionConsumer.accept(dragTouchRegion);
}
private void updateCursorType(int displayId, int deviceId, int pointerId, float x,
float y) {
- @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
+ @DragPositioningCallback.CtrlType int ctrlType =
+ mDragResizeWindowGeometry.calculateCtrlType(/* isTouch= */ false, x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
@@ -640,19 +510,7 @@
}
private boolean shouldHandleEvent(MotionEvent e, Point offset) {
- return shouldHandleEvent(e, isTouchEvent(e), offset);
- }
-
- private boolean shouldHandleEvent(MotionEvent e, boolean isTouch, Point offset) {
- boolean result;
- final float x = e.getX(0) + offset.x;
- final float y = e.getY(0) + offset.y;
- if (isTouch) {
- result = isInCornerBounds(x, y);
- } else {
- result = isInResizeHandleBounds(x, y);
- }
- return result;
+ return mDragResizeWindowGeometry.shouldHandleEvent(e, offset);
}
private boolean isTouchEvent(MotionEvent e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
new file mode 100644
index 0000000..eafb569
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+
+import static com.android.window.flags.Flags.enableWindowingEdgeDragResize;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import android.annotation.NonNull;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Size;
+import android.view.MotionEvent;
+
+import com.android.wm.shell.R;
+
+import java.util.Objects;
+
+/**
+ * Geometry for a drag resize region for a particular window.
+ */
+final class DragResizeWindowGeometry {
+ private final int mTaskCornerRadius;
+ private final Size mTaskSize;
+ // The size of the handle applied to the edges of the window, for the user to drag resize.
+ private final int mResizeHandleThickness;
+ // The task corners to permit drag resizing with a course input, such as touch.
+
+ private final @NonNull TaskCorners mLargeTaskCorners;
+ // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
+ private final @NonNull TaskCorners mFineTaskCorners;
+ // The bounds for each edge drag region, which can resize the task in one direction.
+ private final @NonNull Rect mTopEdgeBounds;
+ private final @NonNull Rect mLeftEdgeBounds;
+ private final @NonNull Rect mRightEdgeBounds;
+ private final @NonNull Rect mBottomEdgeBounds;
+
+ DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
+ int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+ mTaskCornerRadius = taskCornerRadius;
+ mTaskSize = taskSize;
+ mResizeHandleThickness = resizeHandleThickness;
+
+ mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
+ mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
+
+ // Save touch areas for each edge.
+ mTopEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ 0);
+ mLeftEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskSize.getHeight());
+ mRightEdgeBounds = new Rect(
+ mTaskSize.getWidth(),
+ 0,
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ mTaskSize.getHeight());
+ mBottomEdgeBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskSize.getHeight(),
+ mTaskSize.getWidth() + mResizeHandleThickness,
+ mTaskSize.getHeight() + mResizeHandleThickness);
+ }
+
+ /**
+ * Returns the resource value to use for the resize handle on the edge of the window.
+ */
+ static int getResizeEdgeHandleSize(@NonNull Resources res) {
+ return enableWindowingEdgeDragResize()
+ ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+ : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
+ }
+
+ /**
+ * Returns the resource value to use for course input, such as touch, that benefits from a large
+ * square on each of the window's corners.
+ */
+ static int getLargeResizeCornerSize(@NonNull Resources res) {
+ return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
+ }
+
+ /**
+ * Returns the resource value to use for fine input, such as stylus, that can use a smaller
+ * square on each of the window's corners.
+ */
+ static int getFineResizeCornerSize(@NonNull Resources res) {
+ return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
+ }
+
+ /**
+ * Returns the size of the task this geometry is calculated for.
+ */
+ @NonNull Size getTaskSize() {
+ // Safe to return directly since size is immutable.
+ return mTaskSize;
+ }
+
+ /**
+ * Returns the union of all regions that can be touched for drag resizing; the corners window
+ * and window edges.
+ */
+ void union(@NonNull Region region) {
+ // Apply the edge resize regions.
+ region.union(mTopEdgeBounds);
+ region.union(mLeftEdgeBounds);
+ region.union(mRightEdgeBounds);
+ region.union(mBottomEdgeBounds);
+
+ if (enableWindowingEdgeDragResize()) {
+ // Apply the corners as well for the larger corners, to ensure we capture all possible
+ // touches.
+ mLargeTaskCorners.union(region);
+ } else {
+ // Only apply fine corners for the legacy approach.
+ mFineTaskCorners.union(region);
+ }
+ }
+
+ /**
+ * Returns if this MotionEvent should be handled, based on its source and position.
+ */
+ boolean shouldHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
+ return shouldHandleEvent(e, isTouchEvent(e), offset);
+ }
+
+ /**
+ * Returns if this MotionEvent should be handled, based on its source and position.
+ */
+ boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) {
+ final float x = e.getX(0) + offset.x;
+ final float y = e.getY(0) + offset.y;
+
+ if (enableWindowingEdgeDragResize()) {
+ // First check if touch falls within a corner.
+ // Large corner bounds are used for course input like touch, otherwise fine bounds.
+ boolean result = isTouch
+ ? isInCornerBounds(mLargeTaskCorners, x, y)
+ : isInCornerBounds(mFineTaskCorners, x, y);
+ // Check if touch falls within the edge resize handle, since edge resizing can apply
+ // for any input source.
+ if (!result) {
+ result = isInEdgeResizeBounds(x, y);
+ }
+ return result;
+ } else {
+ // Legacy uses only fine corners for touch, and edges only for non-touch input.
+ return isTouch
+ ? isInCornerBounds(mFineTaskCorners, x, y)
+ : isInEdgeResizeBounds(x, y);
+ }
+ }
+
+ private boolean isTouchEvent(@NonNull MotionEvent e) {
+ return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ }
+
+ private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) {
+ return corners.calculateCornersCtrlType(xf, yf) != 0;
+ }
+
+ private boolean isInEdgeResizeBounds(float x, float y) {
+ return calculateEdgeResizeCtrlType(x, y) != 0;
+ }
+
+ /**
+ * Returns the control type for the drag-resize, based on the touch regions and this
+ * MotionEvent's coordinates.
+ */
+ @DragPositioningCallback.CtrlType
+ int calculateCtrlType(boolean isTouch, float x, float y) {
+ if (enableWindowingEdgeDragResize()) {
+ // First check if touch falls within a corner.
+ // Large corner bounds are used for course input like touch, otherwise fine bounds.
+ int ctrlType = isTouch
+ ? mLargeTaskCorners.calculateCornersCtrlType(x, y)
+ : mFineTaskCorners.calculateCornersCtrlType(x, y);
+ // Check if touch falls within the edge resize handle, since edge resizing can apply
+ // for any input source.
+ if (ctrlType == CTRL_TYPE_UNDEFINED) {
+ ctrlType = calculateEdgeResizeCtrlType(x, y);
+ }
+ return ctrlType;
+ } else {
+ // Legacy uses only fine corners for touch, and edges only for non-touch input.
+ return isTouch
+ ? mFineTaskCorners.calculateCornersCtrlType(x, y)
+ : calculateEdgeResizeCtrlType(x, y);
+ }
+ }
+
+ @DragPositioningCallback.CtrlType
+ private int calculateEdgeResizeCtrlType(float x, float y) {
+ int ctrlType = CTRL_TYPE_UNDEFINED;
+ // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
+ // sides will use the bounds specified in setGeometry and not go into task bounds.
+ if (x < mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_LEFT;
+ }
+ if (x > mTaskSize.getWidth() - mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_RIGHT;
+ }
+ if (y < mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_TOP;
+ }
+ if (y > mTaskSize.getHeight() - mTaskCornerRadius) {
+ ctrlType |= CTRL_TYPE_BOTTOM;
+ }
+ // If the touch is within one of the four corners, check if it is within the bounds of the
+ // // handle.
+ if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
+ && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
+ return checkDistanceFromCenter(ctrlType, x, y);
+ }
+ // Otherwise, we should make sure we don't resize tasks inside task bounds.
+ return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
+ ? ctrlType : CTRL_TYPE_UNDEFINED;
+ }
+
+ /**
+ * Return {@code ctrlType} if the corner input is outside the (potentially rounded) corner of
+ * the task, and within the thickness of the resize handle. Otherwise, return 0.
+ */
+ @DragPositioningCallback.CtrlType
+ private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, float x,
+ float y) {
+ final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
+ double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
+
+ if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+ && distanceFromCenter >= mTaskCornerRadius) {
+ return ctrlType;
+ }
+ return CTRL_TYPE_UNDEFINED;
+ }
+
+ /**
+ * Returns center of rounded corner circle; this is simply the corner if radius is 0.
+ */
+ private Point calculateCenterForCornerRadius(@DragPositioningCallback.CtrlType int ctrlType) {
+ int centerX;
+ int centerY;
+
+ switch (ctrlType) {
+ case CTRL_TYPE_LEFT | CTRL_TYPE_TOP: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskCornerRadius;
+ centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP: {
+ centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+ centerY = mTaskCornerRadius;
+ break;
+ }
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM: {
+ centerX = mTaskSize.getWidth() - mTaskCornerRadius;
+ centerY = mTaskSize.getHeight() - mTaskCornerRadius;
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException(
+ "ctrlType should be complex, but it's 0x" + Integer.toHexString(ctrlType));
+ }
+ }
+ return new Point(centerX, centerY);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof DragResizeWindowGeometry other)) return false;
+
+ return this.mTaskCornerRadius == other.mTaskCornerRadius
+ && this.mTaskSize.equals(other.mTaskSize)
+ && this.mResizeHandleThickness == other.mResizeHandleThickness
+ && this.mFineTaskCorners.equals(other.mFineTaskCorners)
+ && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
+ && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
+ && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
+ && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
+ && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mTaskCornerRadius,
+ mTaskSize,
+ mResizeHandleThickness,
+ mFineTaskCorners,
+ mLargeTaskCorners,
+ mTopEdgeBounds,
+ mLeftEdgeBounds,
+ mRightEdgeBounds,
+ mBottomEdgeBounds);
+ }
+
+ /**
+ * Representation of the drag resize regions at the corner of the window.
+ */
+ private static class TaskCorners {
+ // The size of the square applied to the corners of the window, for the user to drag
+ // resize.
+ private final int mCornerSize;
+ // The square for each corner.
+ private final @NonNull Rect mLeftTopCornerBounds;
+ private final @NonNull Rect mRightTopCornerBounds;
+ private final @NonNull Rect mLeftBottomCornerBounds;
+ private final @NonNull Rect mRightBottomCornerBounds;
+
+ TaskCorners(@NonNull Size taskSize, int cornerSize) {
+ mCornerSize = cornerSize;
+ final int cornerRadius = cornerSize / 2;
+ mLeftTopCornerBounds = new Rect(
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
+
+ mRightTopCornerBounds = new Rect(
+ taskSize.getWidth() - cornerRadius,
+ -cornerRadius,
+ taskSize.getWidth() + cornerRadius,
+ cornerRadius);
+
+ mLeftBottomCornerBounds = new Rect(
+ -cornerRadius,
+ taskSize.getHeight() - cornerRadius,
+ cornerRadius,
+ taskSize.getHeight() + cornerRadius);
+
+ mRightBottomCornerBounds = new Rect(
+ taskSize.getWidth() - cornerRadius,
+ taskSize.getHeight() - cornerRadius,
+ taskSize.getWidth() + cornerRadius,
+ taskSize.getHeight() + cornerRadius);
+ }
+
+ /**
+ * Updates the region to include all four corners.
+ */
+ void union(Region region) {
+ region.union(mLeftTopCornerBounds);
+ region.union(mRightTopCornerBounds);
+ region.union(mLeftBottomCornerBounds);
+ region.union(mRightBottomCornerBounds);
+ }
+
+ /**
+ * Returns the control type based on the position of the {@code MotionEvent}'s coordinates.
+ */
+ @DragPositioningCallback.CtrlType
+ int calculateCornersCtrlType(float x, float y) {
+ int xi = (int) x;
+ int yi = (int) y;
+ if (mLeftTopCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
+ }
+ if (mLeftBottomCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
+ }
+ if (mRightTopCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
+ }
+ if (mRightBottomCornerBounds.contains(xi, yi)) {
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
+ }
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "TaskCorners of size " + mCornerSize + " for the"
+ + " top left " + mLeftTopCornerBounds
+ + " top right " + mRightTopCornerBounds
+ + " bottom left " + mLeftBottomCornerBounds
+ + " bottom right " + mRightBottomCornerBounds;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (this == obj) return true;
+ if (!(obj instanceof TaskCorners other)) return false;
+
+ return this.mCornerSize == other.mCornerSize
+ && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds)
+ && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds)
+ && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds)
+ && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mCornerSize,
+ mLeftTopCornerBounds,
+ mRightTopCornerBounds,
+ mLeftBottomCornerBounds,
+ mRightBottomCornerBounds);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
index d0fcd86..1763e4f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskOperations.java
@@ -91,14 +91,12 @@
}
}
- void maximizeTask(RunningTaskInfo taskInfo) {
+ void maximizeTask(RunningTaskInfo taskInfo, int containerWindowingMode) {
WindowContainerTransaction wct = new WindowContainerTransaction();
int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
- int displayWindowingMode =
- taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
wct.setWindowingMode(taskInfo.token,
- targetWindowingMode == displayWindowingMode
+ targetWindowingMode == containerWindowingMode
? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
wct.setBounds(taskInfo.token, null);
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index d64bfed..b85d793 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -33,7 +33,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
index 371fee2..d059211 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -30,7 +30,7 @@
/**
* Test auto entering pip using a source rect hint.
*
- * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:AutoEnterPipWithSourceRectHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 1c0820a..a5e0550 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -31,7 +31,7 @@
/**
* Test closing a pip window by swiping it to the bottom-center of the screen
*
- * To run this test: `atest WMShellFlickerTests:ExitPipWithSwipeDownTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipBySwipingDownTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 860307f..d177624 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -30,7 +30,7 @@
/**
* Test closing a pip window via the dismiss button
*
- * To run this test: `atest WMShellFlickerTests:ExitPipWithDismissButtonTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ClosePipWithDismissButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c554161..a86803d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -31,7 +31,7 @@
/**
* Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
*
- * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 270ebf5..a0a61fe2 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -46,7 +46,7 @@
/**
* Test entering pip while changing orientation (from app in landscape to pip window in portrait)
*
- * To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipToOtherOrientation`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f97d8d1..d92f55a 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test entering pip from an app by interacting with the app UI
*
- * To run this test: `atest WMShellFlickerTests:EnterPipTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:EnterPipViaAppUiButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 47bf418..8c0817d 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via the expand button
*
- * To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaExpandButtonTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index a356e68..90a9623 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -28,7 +28,7 @@
/**
* Test expanding a pip window back to full screen via an intent
*
- * To run this test: `atest WMShellFlickerTests:ExitPipViaIntentTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExitPipToAppViaIntentTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index eeff167..9306c77 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -33,7 +33,7 @@
/**
* Test expanding a pip window by double-clicking it
*
- * To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
+ * To run this test: `atest WMShellFlickerTestsPip2:ExpandPipOnDoubleClickTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
index 12e395d..cb8ee27 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenAutoEnterPipOnGoToHomeTest.kt
@@ -39,7 +39,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenAutoEnterPipOnGoToHomeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
index f81e849..f2f10ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/FromSplitScreenEnterPipOnUserLeaveHintTest.kt
@@ -40,7 +40,7 @@
/**
* Test entering pip from an app via auto-enter property when navigating to home from split screen.
*
- * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:FromSplitScreenEnterPipOnUserLeaveHintTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 9b74622..265eb44 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (increase).
*
- * To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipDownOnShelfHeightChange`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index ad3c69e..04fedf4 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -34,7 +34,10 @@
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/** Test Pip launch. To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */
+/**
+ * Test Pip launch. To run this test:
+ * `atest WMShellFlickerTestsPip3:MovePipOnImeVisibilityChangeTest`
+ */
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 490ebd1..8d6be64 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -32,7 +32,7 @@
/**
* Test Pip movement with Launcher shelf height change (decrease).
*
- * To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
+ * To run this test: `atest WMShellFlickerTestsPip3:MovePipUpOnShelfHeightChangeTest`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 9a6dacb..ed2a0a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -41,8 +41,8 @@
import org.junit.runners.Parameterized
/**
- * Test exiting Pip with orientation changes. To run this test: `atest
- * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test:
+ * `atest WMShellFlickerTestsPip1:SetRequestedOrientationWhilePinned`
*/
@RequiresDevice
@RunWith(Parameterized::class)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index d2f803e..9109eaf 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -35,7 +35,7 @@
/**
* Test Pip Stack in bounds after rotations.
*
- * To run this test: `atest WMShellFlickerTests:PipRotationTest`
+ * To run this test: `atest WMShellFlickerTestsPip1:ShowPipAndRotateDisplay`
*
* Actions:
* ```
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 32c0703..13f95cc 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -39,7 +39,7 @@
static_libs: [
"WindowManager-Shell",
"junit",
- "flag-junit-base",
+ "flag-junit",
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
@@ -55,6 +55,9 @@
"platform-test-annotations",
"servicestests-utils",
"com_android_wm_shell_flags_lib",
+ "guava-android-testlib",
+ "com.android.window.flags.window-aconfig-java",
+ "platform-test-annotations",
],
libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 4061763..65169e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -596,6 +596,7 @@
// Set up the monitoring objects.
doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+ doReturn(false).when(animationRunner).shouldMonitorCUJ(any());
doReturn(runner).when(animationRunner).getRunner();
doReturn(callback).when(animationRunner).getCallback();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 93a967e..4d0f11b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -161,6 +161,10 @@
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+
controller = createController()
controller.setSplitScreenController(splitScreenController)
@@ -336,9 +340,10 @@
}
@Test
- fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
+ fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
val task = setUpFullscreenTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -346,9 +351,10 @@
}
@Test
- fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
+ fun moveToDesktop_tdaFreeform_windowingModeSetToUndefined() {
val task = setUpFullscreenTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
controller.moveToDesktop(task)
val wct = getLatestMoveToDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -481,9 +487,10 @@
}
@Test
- fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
+ fun moveToFullscreen_tdaFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -491,9 +498,10 @@
}
@Test
- fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
+ fun moveToFullscreen_tdaFreeform_windowingModeSetToFullscreen() {
val task = setUpFreeformTask()
- task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
controller.moveToFullscreen(task.taskId)
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
@@ -684,7 +692,7 @@
createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
)
assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -694,7 +702,7 @@
val task = createFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -706,7 +714,7 @@
val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay))
assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -792,7 +800,7 @@
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
@@ -895,7 +903,7 @@
val wct = getLatestExitDesktopWct()
assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
- .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index d38e97f..40b59c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -35,6 +35,7 @@
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -45,16 +46,21 @@
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Bundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -70,6 +76,7 @@
import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -99,6 +106,11 @@
private ActivityTaskManager mActivityTaskManager;
@Mock
private DisplayInsetsController mDisplayInsetsController;
+ @Mock
+ private IRecentTasksListener mRecentTasksListener;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
@@ -426,6 +438,85 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskAdded_desktopModeRunningAppsEnabled_triggersOnRunningTaskAppeared()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskAppeared(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void onTaskAdded_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskAppeared()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskAdded(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskAppeared(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void taskWindowingModeChanged_desktopRunningAppsEnabled_triggersOnRunningTaskChanged()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskChanged(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void
+ taskWindowingModeChanged_desktopRunningAppsDisabled_doesNotTriggerOnRunningTaskChanged()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskWindowingModeChanged(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskChanged(any());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS})
+ public void onTaskRemoved_desktopModeRunningAppsEnabled_triggersOnRunningTaskVanished()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener).onRunningTaskVanished(taskInfo);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS)
+ public void onTaskRemoved_desktopModeRunningAppsDisabled_doesNotTriggerOnRunningTaskVanished()
+ throws Exception {
+ mRecentTasksControllerReal.registerRecentTasksListener(mRecentTasksListener);
+ ActivityManager.RunningTaskInfo taskInfo = makeRunningTaskInfo(/* taskId= */10);
+
+ mRecentTasksControllerReal.onTaskRemoved(taskInfo);
+
+ verify(mRecentTasksListener, never()).onRunningTaskVanished(any());
+ }
+
+ @Test
public void getNullSplitBoundsNonSplitTask() {
SplitBounds sb = mRecentTasksController.getSplitBoundsForTaskId(3);
assertNull("splitBounds should be null for non-split task", sb);
@@ -471,6 +562,7 @@
private ActivityManager.RunningTaskInfo makeRunningTaskInfo(int taskId) {
ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.taskId = taskId;
+ info.realActivity = new ComponentName("testPackage", "testClass");
return info;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
new file mode 100644
index 0000000..82e5a1c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Region;
+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.testing.AndroidTestingRunner;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.window.flags.Flags;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DragResizeWindowGeometry}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DragResizeWindowGeometryTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DragResizeWindowGeometryTests {
+ private static final Size TASK_SIZE = new Size(500, 1000);
+ private static final int TASK_CORNER_RADIUS = 10;
+ private static final int EDGE_RESIZE_THICKNESS = 15;
+ private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
+ private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
+ private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
+ TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE);
+ // Points in the edge resize handle. Note that coordinates start from the top left.
+ private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+ -EDGE_RESIZE_THICKNESS / 2);
+ private static final Point LEFT_EDGE_POINT = new Point(-EDGE_RESIZE_THICKNESS / 2,
+ TASK_SIZE.getHeight() / 2);
+ private static final Point RIGHT_EDGE_POINT = new Point(
+ TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2);
+ private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
+ TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Check that both groups of objects satisfy equals/hashcode within each group, and that each
+ * group is distinct from the next.
+ */
+ @Test
+ public void testEqualsAndHash() {
+ new EqualsTester()
+ .addEqualityGroup(
+ GEOMETRY,
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+ .addEqualityGroup(
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+ .addEqualityGroup(
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5),
+ new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
+ EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+ LARGE_CORNER_SIZE + 5))
+ .testEquals();
+ }
+
+ @Test
+ public void testGetTaskSize() {
+ assertThat(GEOMETRY.getTaskSize()).isEqualTo(TASK_SIZE);
+ }
+
+ @Test
+ public void testRegionUnionContainsEdges() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ assertThat(region.isComplex()).isTrue();
+ // Region excludes task area. Note that coordinates start from top left.
+ assertThat(region.contains(TASK_SIZE.getWidth() / 2, TASK_SIZE.getHeight() / 2)).isFalse();
+ // Region includes edges outside the task window.
+ verifyVerticalEdge(region, LEFT_EDGE_POINT);
+ verifyHorizontalEdge(region, TOP_EDGE_POINT);
+ verifyVerticalEdge(region, RIGHT_EDGE_POINT);
+ verifyHorizontalEdge(region, BOTTOM_EDGE_POINT);
+ }
+
+ private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) {
+ assertThat(region.contains(point.x, point.y)).isTrue();
+ // Horizontally along the edge is still contained.
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
+ // Vertically along the edge is not contained.
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+ }
+
+ private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
+ assertThat(region.contains(point.x, point.y)).isTrue();
+ // Horizontally along the edge is not contained.
+ assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ // Vertically along the edge is contained.
+ assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
+ assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
+ }
+
+ /**
+ * Validate that with the flag enabled, the corner resize regions are the largest size, to
+ * capture all eligible input regardless of source (touch or cursor).
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ final int cornerRadius = LARGE_CORNER_SIZE / 2;
+
+ new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ }
+
+ /**
+ * Validate that with the flag disabled, the corner resize regions are the original smaller
+ * size.
+ */
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() {
+ Region region = new Region();
+ GEOMETRY.union(region);
+ final int cornerRadius = FINE_CORNER_SIZE / 2;
+
+ new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeEnabled_edges() {
+ // The input source (touch or cursor) shouldn't impact the edge resize size.
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ validateCtrlTypeForEdges(/* isTouch= */ true);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeDisabled_edges() {
+ // Edge resizing is not supported when the flag is disabled.
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ validateCtrlTypeForEdges(/* isTouch= */ false);
+ }
+
+ private void validateCtrlTypeForEdges(boolean isTouch) {
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, LEFT_EDGE_POINT.x,
+ LEFT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_LEFT);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, TOP_EDGE_POINT.x,
+ TOP_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_TOP);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, RIGHT_EDGE_POINT.x,
+ RIGHT_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_RIGHT);
+ assertThat(GEOMETRY.calculateCtrlType(isTouch, BOTTOM_EDGE_POINT.x,
+ BOTTOM_EDGE_POINT.y)).isEqualTo(CTRL_TYPE_BOTTOM);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeEnabled_corners() {
+ final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+ // When the flag is enabled, points within fine corners should pass regardless of touch or
+ // not. Points outside fine corners should not pass when using a course input (non-touch).
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+ // When the flag is enabled, points near the large corners should only pass when the point
+ // is within the corner for large touch inputs.
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+ false);
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+ false);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE)
+ public void testCalculateControlType_edgeDragResizeDisabled_corners() {
+ final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2);
+ final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2);
+
+ // When the flag is disabled, points within fine corners should pass only when touch.
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, true);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true, false);
+ fineTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ fineTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false, false);
+
+ // When the flag is disabled, points near the large corners should never pass.
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ true, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ true,
+ false);
+ largeCornerTestPoints.validateCtrlTypeForInnerPoints(GEOMETRY, /* isTouch= */ false, false);
+ largeCornerTestPoints.validateCtrlTypeForOutsidePoints(GEOMETRY, /* isTouch= */ false,
+ false);
+ }
+
+ /**
+ * Class for creating points for testing the drag resize corners.
+ *
+ * <p>Creates points that are both just within the bounds of each corner, and just outside.
+ */
+ private static final class TestPoints {
+ private final Point mTopLeftPoint;
+ private final Point mTopLeftPointOutside;
+ private final Point mTopRightPoint;
+ private final Point mTopRightPointOutside;
+ private final Point mBottomLeftPoint;
+ private final Point mBottomLeftPointOutside;
+ private final Point mBottomRightPoint;
+ private final Point mBottomRightPointOutside;
+
+ TestPoints(@NonNull Size taskSize, int cornerRadius) {
+ // Point just inside corner square is included.
+ mTopLeftPoint = new Point(-cornerRadius + 1, -cornerRadius + 1);
+ // Point just outside corner square is excluded.
+ mTopLeftPointOutside = new Point(mTopLeftPoint.x - 5, mTopLeftPoint.y - 5);
+
+ mTopRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1, -cornerRadius + 1);
+ mTopRightPointOutside = new Point(mTopRightPoint.x + 5, mTopRightPoint.y - 5);
+
+ mBottomLeftPoint = new Point(-cornerRadius + 1,
+ taskSize.getHeight() + cornerRadius - 1);
+ mBottomLeftPointOutside = new Point(mBottomLeftPoint.x - 5, mBottomLeftPoint.y + 5);
+
+ mBottomRightPoint = new Point(taskSize.getWidth() + cornerRadius - 1,
+ taskSize.getHeight() + cornerRadius - 1);
+ mBottomRightPointOutside = new Point(mBottomRightPoint.x + 5, mBottomRightPoint.y + 5);
+ }
+
+ /**
+ * Validates that all test points are either within or without the given region.
+ */
+ public void validateRegion(@NonNull Region region) {
+ // Point just inside corner square is included.
+ assertThat(region.contains(mTopLeftPoint.x, mTopLeftPoint.y)).isTrue();
+ // Point just outside corner square is excluded.
+ assertThat(region.contains(mTopLeftPointOutside.x, mTopLeftPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mTopRightPoint.x, mTopRightPoint.y)).isTrue();
+ assertThat(
+ region.contains(mTopRightPointOutside.x, mTopRightPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mBottomLeftPoint.x, mBottomLeftPoint.y)).isTrue();
+ assertThat(region.contains(mBottomLeftPointOutside.x,
+ mBottomLeftPointOutside.y)).isFalse();
+
+ assertThat(region.contains(mBottomRightPoint.x, mBottomRightPoint.y)).isTrue();
+ assertThat(region.contains(mBottomRightPointOutside.x,
+ mBottomRightPointOutside.y)).isFalse();
+ }
+
+ /**
+ * Validates that all test points within this drag corner size give the correct
+ * {@code @DragPositioningCallback.CtrlType}.
+ */
+ public void validateCtrlTypeForInnerPoints(@NonNull DragResizeWindowGeometry geometry,
+ boolean isTouch, boolean expectedWithinGeometry) {
+ assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPoint.x,
+ mTopLeftPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mTopRightPoint.x,
+ mTopRightPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPoint.x,
+ mBottomLeftPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPoint.x,
+ mBottomRightPoint.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ }
+
+ /**
+ * Validates that all test points outside this drag corner size give the correct
+ * {@code @DragPositioningCallback.CtrlType}.
+ */
+ public void validateCtrlTypeForOutsidePoints(@NonNull DragResizeWindowGeometry geometry,
+ boolean isTouch, boolean expectedWithinGeometry) {
+ assertThat(geometry.calculateCtrlType(isTouch, mTopLeftPointOutside.x,
+ mTopLeftPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mTopRightPointOutside.x,
+ mTopRightPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomLeftPointOutside.x,
+ mBottomLeftPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ assertThat(geometry.calculateCtrlType(isTouch, mBottomRightPointOutside.x,
+ mBottomRightPointOutside.y)).isEqualTo(
+ expectedWithinGeometry ? CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM
+ : CTRL_TYPE_UNDEFINED);
+ }
+ }
+}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 34a6bc2..839c7b6 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -119,30 +119,41 @@
* appear to be bogus.
*/
bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32) const
+{
+ return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen,
+ pCrc32, nullptr);
+}
+
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
- uint32_t* pModWhen, uint32_t* pCrc32) const
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const
{
const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
const ZipEntry& ze = zipEntry->entry;
- if (pMethod != NULL) {
+ if (pMethod != nullptr) {
*pMethod = ze.method;
}
- if (pUncompLen != NULL) {
+ if (pUncompLen != nullptr) {
*pUncompLen = ze.uncompressed_length;
}
- if (pCompLen != NULL) {
+ if (pCompLen != nullptr) {
*pCompLen = ze.compressed_length;
}
- if (pOffset != NULL) {
+ if (pOffset != nullptr) {
*pOffset = ze.offset;
}
- if (pModWhen != NULL) {
+ if (pModWhen != nullptr) {
*pModWhen = ze.mod_time;
}
- if (pCrc32 != NULL) {
+ if (pCrc32 != nullptr) {
*pCrc32 = ze.crc32;
}
+ if (pExtraFieldSize != nullptr) {
+ *pExtraFieldSize = ze.extra_field_size;
+ }
return true;
}
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index 031d2e8..f7c5007 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -151,6 +151,10 @@
uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen,
uint32_t* pCrc32) const;
+ bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const;
+
/*
* Create a new FileMap object that maps a subset of the archive. For
* an uncompressed entry this effectively provides a pointer to the
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 34932b1..dc669a5 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -24,7 +24,6 @@
#include <SkImageAndroid.h>
#include <SkImageInfo.h>
#include <SkMatrix.h>
-#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
#include <SkPicture.h>
@@ -38,6 +37,7 @@
#include <android-base/properties.h>
#include <gui/TraceUtils.h>
#include <include/android/SkSurfaceAndroid.h>
+#include <include/docs/SkMultiPictureDocument.h>
#include <include/encode/SkPngEncoder.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <unistd.h>
@@ -185,7 +185,7 @@
// we need to keep it until after mMultiPic.close()
// procs is passed as a pointer, but just as a method of having an optional default.
// procs doesn't need to outlive this Make call.
- mMultiPic = SkMakeMultiPictureDocument(mOpenMultiPicStream.get(), &procs,
+ mMultiPic = SkMultiPictureDocument::Make(mOpenMultiPicStream.get(), &procs,
[sharingCtx = mSerialContext.get()](const SkPicture* pic) {
SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
});
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index cf14b1f..823b209 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -18,7 +18,6 @@
#include <SkColorSpace.h>
#include <SkDocument.h>
-#include <SkMultiPictureDocument.h>
#include <SkSurface.h>
#include "Lighting.h"
diff --git a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
index b62711f..21fe6ff 100644
--- a/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkFunctorDrawable.cpp
@@ -16,10 +16,10 @@
#include "VkFunctorDrawable.h"
-#include <GrBackendDrawableInfo.h>
#include <SkAndroidFrameworkUtils.h>
#include <SkImage.h>
#include <SkM44.h>
+#include <include/gpu/ganesh/vk/GrBackendDrawableInfo.h>
#include <gui/TraceUtils.h>
#include <private/hwui/DrawVkInfo.h>
#include <utils/Color.h>
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5aa006b..c664d3d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -2713,7 +2713,7 @@
List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
- transfer(targetSession, route, Process.myUserHandle(), mContext.getPackageName());
+ transfer(targetSession, route, mClientUser, mContext.getPackageName());
}
@Override
diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java
index bbf1e08..d788284 100644
--- a/media/java/android/media/session/ParcelableListBinder.java
+++ b/media/java/android/media/session/ParcelableListBinder.java
@@ -45,6 +45,7 @@
private static final int END_OF_PARCEL = 0;
private static final int ITEM_CONTINUED = 1;
+ private final Class<T> mListElementsClass;
private final Consumer<List<T>> mConsumer;
private final Object mLock = new Object();
@@ -61,9 +62,11 @@
/**
* Creates an instance.
*
+ * @param listElementsClass the class of the list elements.
* @param consumer a consumer that consumes the list received
*/
- public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) {
+ public ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer) {
+ mListElementsClass = listElementsClass;
mConsumer = consumer;
}
@@ -83,7 +86,13 @@
mCount = data.readInt();
}
while (i < mCount && data.readInt() != END_OF_PARCEL) {
- mList.add(data.readParcelable(null));
+ Object object = data.readParcelable(null);
+ if (mListElementsClass.isAssignableFrom(object.getClass())) {
+ // Checking list items are of compaitible types to validate against malicious
+ // apps calling it directly via reflection with non compilable items.
+ // See b/317048338 for more details
+ mList.add((T) object);
+ }
i++;
}
if (i >= mCount) {
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 9fd386f..b6b1a45 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -68,14 +68,6 @@
<string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
<!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
<string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="app_name" example="Tribank">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create passkey flow. [CHAR LIMIT=200] -->
- <string name="choose_create_single_tap_passkey_title">Use your screen lock to create a passkey for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create password flow. [CHAR LIMIT=200] -->
- <string name="choose_create_single_tap_password_title">Use your screen lock to create a password for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- This appears as a description of the modal bottom sheet when the single tap sign in flow is used for the create flow when the credential type is others. [CHAR LIMIT=200] -->
- <!-- TODO(b/326243891) : Confirm with team on dynamically setting this based on recent product and ux discussions (does not disrupt e2e) -->
- <string name="choose_create_single_tap_sign_in_title">Use your screen lock to save sign in info for <xliff:g id="app_name" example="Shrine">%1$s</xliff:g>?</string>
- <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
<string name="passkey">passkey</string>
<string name="password">password</string>
<string name="passkeys">passkeys</string>
@@ -133,6 +125,12 @@
<string name="get_dialog_title_single_tap_for">Use your screen lock to sign in to <xliff:g id="app_name" example="Shrine">%1$s</xliff:g> with <xliff:g id="username" example="beckett-bakery@gmail.com">%2$s</xliff:g></string>
<!-- This appears as the title of the dialog asking for user confirmation to use the single user credential (previously saved or to be created) to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_use_sign_in_for">Use your sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
+ <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for passkey authentication. [CHAR LIMIT=200] -->
+ <string name="get_dialog_description_single_tap_passkey">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved passkey for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+ <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for password authentication. [CHAR LIMIT=200] -->
+ <string name="get_dialog_description_single_tap_password">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved password for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
+ <!-- This appears as the description of the modal bottom sheet asking for user confirmation to use the biometric screen embedded within credential manager for saved sign-in authentication. [CHAR LIMIT=200] -->
+ <string name="get_dialog_description_single_tap_saved_sign_in">Sign in to <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> with your saved sign-in info for <xliff:g id="username" example="beckett@gmail.com">%2$s</xliff:g>.</string>
<!-- This appears as the title of the dialog asking for user confirmation to unlock / authenticate (e.g. via fingerprint, faceId, passcode etc.) so that we can retrieve their sign-in options. [CHAR LIMIT=200] -->
<string name="get_dialog_title_unlock_options_for">Unlock sign-in options for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking for user to make a choice from multiple previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index ab70394..694e27a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -21,7 +21,6 @@
import android.content.Intent
import android.credentials.selection.BaseDialogResult
import android.credentials.selection.BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED
-import android.credentials.selection.Constants
import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
import android.os.Bundle
@@ -117,21 +116,17 @@
sendCancellationCode(
cancelCode = cancelCode,
requestToken = token,
- resultReceiver = resultReceiver,
- finalResponseReceiver = finalResponseReceiver
+ resultReceiver = resultReceiver
)
}
private fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
- resultReceiver: ResultReceiver?,
- finalResponseReceiver: ResultReceiver?
+ resultReceiver: ResultReceiver?
) {
if (requestToken != null && resultReceiver != null) {
- val resultData = Bundle().apply {
- putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER, finalResponseReceiver)
- }
+ val resultData = Bundle()
BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
resultReceiver.send(cancelCode, resultData)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 786c441..9242141 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -54,9 +54,3 @@
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
-
-val Intent.finalResponseReceiver: ResultReceiver?
- get() = this.getParcelableExtra(
- Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index 1683cc4..f1f1f7c 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -20,7 +20,6 @@
import android.content.Intent
import com.android.credentialmanager.ktx.getCredentialProviderDataList
import com.android.credentialmanager.ktx.requestInfo
-import com.android.credentialmanager.ktx.finalResponseReceiver
import com.android.credentialmanager.ktx.resultReceiver
import com.android.credentialmanager.ktx.toProviderList
import com.android.credentialmanager.model.Request
@@ -29,7 +28,6 @@
return Request.Get(
token = requestInfo?.token,
resultReceiver = resultReceiver,
- finalResponseReceiver = finalResponseReceiver,
providerInfos = getCredentialProviderDataList.toProviderList(context)
)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index fd99275..cb335fc 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,8 +25,7 @@
*/
sealed class Request private constructor(
open val token: IBinder?,
- open val resultReceiver: ResultReceiver? = null,
- open val finalResponseReceiver: ResultReceiver? = null,
+ open val resultReceiver: ResultReceiver? = null
) {
/**
@@ -51,9 +50,8 @@
data class Get(
override val token: IBinder?,
override val resultReceiver: ResultReceiver?,
- override val finalResponseReceiver: ResultReceiver?,
val providerInfos: List<ProviderInfo>,
- ) : Request(token, resultReceiver, finalResponseReceiver)
+ ) : Request(token, resultReceiver)
/**
* Request to start the create credentials flow.
*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index b17a98b..3683235 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -58,7 +58,6 @@
private val providerEnabledList: List<ProviderData>
private val providerDisabledList: List<DisabledProviderData>?
val resultReceiver: ResultReceiver?
- val finalResponseReceiver: ResultReceiver?
var initialUiState: UiState
@@ -105,12 +104,6 @@
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
-
- finalResponseReceiver = intent.getParcelableExtra(
- Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
-
isReqForAllOptions = requestInfo?.isShowAllOptionsRequested ?: false
val cancellationRequest = getCancelUiRequest(intent)
@@ -206,7 +199,7 @@
}
fun onCancel(cancelCode: Int) {
- sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver, finalResponseReceiver)
+ sendCancellationCode(cancelCode, requestInfo?.token, resultReceiver)
}
fun onOptionSelected(
@@ -226,9 +219,6 @@
val resultDataBundle = Bundle()
UserSelectionDialogResult.addToBundle(userSelectionDialogResult, resultDataBundle)
- resultDataBundle.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- finalResponseReceiver)
-
resultReceiver?.send(
BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
resultDataBundle
@@ -296,13 +286,10 @@
fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
- resultReceiver: ResultReceiver?,
- finalResponseReceiver: ResultReceiver?
+ resultReceiver: ResultReceiver?
) {
if (requestToken != null && resultReceiver != null) {
val resultData = Bundle()
- resultData.putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- finalResponseReceiver)
BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
resultReceiver.send(cancelCode, resultData)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index ec0da09..a2f55cd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -208,18 +208,13 @@
android.credentials.selection.Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
- val finalResponseResultReceiver = intent.getParcelableExtra(
- android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver::class.java
- )
-
val requestInfo = intent.extras?.getParcelable(
RequestInfo.EXTRA_REQUEST_INFO,
RequestInfo::class.java
)
CredentialManagerRepo.sendCancellationCode(
BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE,
- requestInfo?.token, resultReceiver, finalResponseResultReceiver
+ requestInfo?.token, resultReceiver
)
this.finish()
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 888777e..429bdbf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -30,6 +30,7 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.BiometricResult
@@ -128,13 +129,22 @@
uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
val entryIntent = entry.fillInIntent
entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
- if (biometricState.biometricResult != null) {
+ if (biometricState.biometricResult != null || biometricState.biometricError != null) {
if (uiState.isAutoSelectFlow) {
Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " +
"autoSelect is preferred.")
}
- entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_TYPE,
- biometricState.biometricResult.biometricAuthenticationResult.authenticationType)
+ // TODO(b/333445754) : Decide whether to propagate info on prompt launch
+ if (biometricState.biometricResult != null) {
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT,
+ biometricState.biometricResult.biometricAuthenticationResult
+ .authenticationType)
+ } else if (biometricState.biometricError != null){
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE,
+ biometricState.biometricError.errorCode)
+ entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE,
+ biometricState.biometricError.errorMessage)
+ }
}
val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
.setFillInIntent(entryIntent).build()
@@ -219,7 +229,8 @@
/**************************************************************************/
fun getFlowOnEntrySelected(
entry: EntryInfo,
- authResult: BiometricPrompt.AuthenticationResult? = null
+ authResult: BiometricPrompt.AuthenticationResult? = null,
+ authError: BiometricError? = null,
) {
Log.d(Constants.LOG_TAG, "credential selected: {provider=${entry.providerId}" +
", key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
@@ -227,10 +238,11 @@
uiState.copy(
selectedEntry = entry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
- biometricState = if (authResult == null) uiState.biometricState else uiState
+ biometricState = if (authResult == null && authError == null)
+ uiState.biometricState else if (authResult != null) uiState
.biometricState.copy(biometricResult = BiometricResult(
- biometricAuthenticationResult = authResult)
- )
+ biometricAuthenticationResult = authResult)) else uiState
+ .biometricState.copy(biometricError = authError)
)
} else {
credManRepo.onOptionSelected(entry.providerId, entry.entryKey, entry.entrySubkey)
@@ -258,6 +270,15 @@
)
}
+ fun getFlowOnMoreOptionOnlySelected() {
+ Log.d(Constants.LOG_TAG, "More Option Only selected")
+ uiState = uiState.copy(
+ getCredentialUiState = uiState.getCredentialUiState?.copy(
+ currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
+ )
+ )
+ }
+
fun getFlowOnMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
Log.d(Constants.LOG_TAG, "More Option on snackBar selected")
uiState = uiState.copy(
@@ -350,7 +371,8 @@
fun createFlowOnEntrySelected(
selectedEntry: EntryInfo,
- authResult: AuthenticationResult? = null
+ authResult: AuthenticationResult? = null,
+ authError: BiometricError? = null,
) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
@@ -362,9 +384,11 @@
uiState = uiState.copy(
selectedEntry = selectedEntry,
providerActivityState = ProviderActivityState.READY_TO_LAUNCH,
- biometricState = if (authResult == null) uiState.biometricState else uiState
+ biometricState = if (authResult == null && authError == null)
+ uiState.biometricState else if (authResult != null) uiState
.biometricState.copy(biometricResult = BiometricResult(
- biometricAuthenticationResult = authResult))
+ biometricAuthenticationResult = authResult)) else uiState
+ .biometricState.copy(biometricError = authError)
)
} else {
credManRepo.onOptionSelected(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 1253ce3..4109079 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -32,6 +32,7 @@
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.os.ResultReceiver
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -109,17 +110,19 @@
}
val sessionId = clientState.getInt(SESSION_ID_KEY)
val requestId = clientState.getInt(REQUEST_ID_KEY)
+ val resultReceiver = clientState.getParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver::class.java)
Log.i(TAG, "Autofill sessionId: $sessionId, autofill requestId: $requestId")
- if (sessionId == 0 || requestId == 0) {
- Log.i(TAG, "Session Id or request Id not found")
- callback.onFailure("Session Id or request Id not found")
+ if (sessionId == 0 || requestId == 0 || resultReceiver == null) {
+ Log.i(TAG, "Session Id or request Id or resultReceiver not found")
+ callback.onFailure("Session Id or request Id or resultReceiver not found")
return
}
val responseClientState = Bundle()
responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false)
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId, responseClientState)
+ requestId, resultReceiver, responseClientState)
// 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,
@@ -531,6 +534,7 @@
structure: AssistStructure,
sessionId: Int,
requestId: Int,
+ resultReceiver: ResultReceiver,
responseClientState: Bundle
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
@@ -540,6 +544,9 @@
val dataBundle = Bundle()
dataBundle.putInt(SESSION_ID_KEY, sessionId)
dataBundle.putInt(REQUEST_ID_KEY, requestId)
+ dataBundle.putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER,
+ resultReceiver)
+
return GetCredentialRequest.Builder(dataBundle)
.setCredentialOptions(credentialOptions)
.build()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index be3e043..95f49e9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -17,9 +17,12 @@
package com.android.credentialmanager.common
import android.content.Context
+import android.content.DialogInterface
import android.graphics.Bitmap
import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton
import android.os.CancellationSignal
import android.util.Log
import androidx.core.content.ContextCompat.getMainExecutor
@@ -43,19 +46,23 @@
* Namely, this adds the ability to encapsulate the [providerIcon], the providers icon, the
* [providerName], which represents the name of the provider, the [displayTitleText] which is
* the large text displaying the flow in progress, and the [descriptionForCredential], which
- * describes details of where the credential is being saved, and how.
- * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com:
+ * describes details of where the credential is being saved, and how. [displaySubtitleText] is only expected
+ * to be used by the 'create' flow, optionally, and describes the saved name of the creating entity.
+ * (E.g. assume a hypothetical provider 'Any Provider' for *passkey* flows with Your@Email.com and
+ * name 'Your', and an rp called 'The App'):
*
* 'get' flow:
* - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
- * - [displayTitleText] = "Use your saved passkey for Any Provider?"
- * - [descriptionForCredential] = "Use your screen lock to sign in to Any Provider with
- * Your@Email.com"
+ * - [displayTitleText] = "Use your saved passkey for The App?"
+ * - [descriptionForCredential] = "Sign in to The App with your saved passkey for
+ * Your@gmail.com"
*
* 'create' flow:
* - [providerIcon] and [providerName] = 'Any Provider' (and it's icon)
* - [displayTitleText] = "Create passkey to sign in to Any Provider?"
- * - [descriptionForCredential] = "Use your screen lock to create a passkey for Any Provider?"
+ * - [subtitle] = "Your"
+ * - [descriptionForCredential] = "You can use your passkey on other devices. It is saved to
+ * * Google Password Manager for Your@gmail.com."
* ).
*
* The above are examples; the credential type can change depending on scenario.
@@ -65,8 +72,9 @@
val providerIcon: Bitmap,
val providerName: String,
val displayTitleText: String,
- val descriptionForCredential: String,
+ val descriptionForCredential: String?,
val biometricRequestInfo: BiometricRequestInfo,
+ val displaySubtitleText: CharSequence? = null,
)
/**
@@ -76,6 +84,7 @@
*/
data class BiometricState(
val biometricResult: BiometricResult? = null,
+ val biometricError: BiometricError? = null,
val biometricStatus: BiometricPromptState = BiometricPromptState.INACTIVE
)
@@ -84,7 +93,7 @@
* so that should this object exist, the result will be retrievable.
*/
data class BiometricResult(
- val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult
+ val biometricAuthenticationResult: BiometricPrompt.AuthenticationResult,
)
/**
@@ -92,16 +101,7 @@
*/
data class BiometricError(
val errorCode: Int,
- val errString: CharSequence? = null
-)
-
-/**
- * Encapsulates the help callback results to easily manage biometric help states in the flow.
- * To specify, this allows us to parse the onAuthenticationHelp method in the [BiometricPrompt].
- */
-data class BiometricHelp(
- val helpCode: Int,
- var helpString: CharSequence? = null
+ val errorMessage: CharSequence? = null
)
/**
@@ -113,7 +113,7 @@
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
@@ -146,7 +146,7 @@
Log.d(TAG, "The BiometricPrompt API call begins.")
runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
- onBiometricFailureFallback, BiometricFlowType.GET)
+ onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish)
}
/**
@@ -158,7 +158,7 @@
biometricEntry: EntryInfo,
context: Context,
openMoreOptionsPage: () -> Unit,
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
@@ -190,14 +190,15 @@
Log.d(TAG, "The BiometricPrompt API call begins.")
runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
- onBiometricFailureFallback, BiometricFlowType.CREATE)
+ onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish)
}
/**
* This will handle the logic for integrating credential manager with the biometric prompt for the
* single account biometric experience. This simultaneously handles both the get and create flows,
* by retrieving all the data from credential manager, and properly parsing that data into the
- * biometric prompt.
+ * biometric prompt. It will fallback in cases where the biometric api cannot be called, or when
+ * only device credentials are requested.
*/
private fun runBiometricFlow(
context: Context,
@@ -205,28 +206,98 @@
callback: BiometricPrompt.AuthenticationCallback,
openMoreOptionsPage: () -> Unit,
onBiometricFailureFallback: (BiometricFlowType) -> Unit,
- biometricFlowType: BiometricFlowType
+ biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit
) {
- val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo, openMoreOptionsPage,
- biometricDisplayInfo.biometricRequestInfo, biometricFlowType)
-
- val cancellationSignal = CancellationSignal()
- cancellationSignal.setOnCancelListener {
- Log.d(TAG, "Your cancellation signal was called.")
- // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
- // or validate the necessity for this
- }
-
- val executor = getMainExecutor(context)
-
try {
- biometricPrompt.authenticate(cancellationSignal, executor, callback)
- } catch (e: IllegalArgumentException) {
+ if (onlyUsingDeviceCredentials(biometricDisplayInfo, context)) {
+ onBiometricFailureFallback(biometricFlowType)
+ return
+ }
+
+ val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
+ openMoreOptionsPage, biometricDisplayInfo.biometricRequestInfo, onCancelFlowAndFinish)
+
+ val cancellationSignal = CancellationSignal()
+ cancellationSignal.setOnCancelListener {
+ Log.d(TAG, "Your cancellation signal was called.")
+ // TODO(b/333445112) : Migrate towards passing along the developer cancellation signal
+ // or validate the necessity for this
+ }
+
+ val executor = getMainExecutor(context)
+
+ val cryptoOpId = getCryptoOpId(biometricDisplayInfo)
+ if (cryptoOpId != null) {
+ biometricPrompt.authenticate(
+ BiometricPrompt.CryptoObject(cryptoOpId.toLong()),
+ cancellationSignal, executor, callback)
+ } else {
+ biometricPrompt.authenticate(cancellationSignal, executor, callback)
+ }
+ } catch (e: Exception) {
+ // TODO(b/334923201) : Specialize exception catching
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
onBiometricFailureFallback(biometricFlowType)
}
}
+private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
+ return biometricDisplayInfo.biometricRequestInfo.opId
+}
+
+/**
+ * Determines if, given the allowed authenticators, the flow should fallback early. This has
+ * consistency because for biometrics to exist, **device credentials must exist**. Thus, fallbacks
+ * occur if *only* device credentials are available, to avoid going right into the PIN screen.
+ * Note that if device credential is the only available modality but not requested, or if none
+ * of the requested modalities are available, we propagate the error to the provider instead of
+ * falling back and expect them to handle it as they would prior.
+ * // TODO(b/334197980) : Finalize error propagation/not propagation in real use cases
+ */
+private fun onlyUsingDeviceCredentials(
+ biometricDisplayInfo: BiometricDisplayInfo,
+ context: Context
+): Boolean {
+ val allowedAuthenticators = biometricDisplayInfo.biometricRequestInfo.allowedAuthenticators
+ if (allowedAuthenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
+ return true
+ }
+
+ val allowedAuthContainsDeviceCredential = containsBiometricAuthenticatorWithDeviceCredentials(
+ allowedAuthenticators)
+
+ if (!allowedAuthContainsDeviceCredential) {
+ // At this point, allowed authenticators is requesting biometrics without device creds.
+ // Thus, a fallback mechanism will be displayed via our own negative button - "cancel".
+ // Beyond this point, fallbacks will occur if none of the stronger authenticators can
+ // be used.
+ return false
+ }
+
+ val biometricManager = context.getSystemService(Context.BIOMETRIC_SERVICE) as BiometricManager
+
+ if (allowedAuthContainsDeviceCredential &&
+ biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK) !=
+ BiometricManager.BIOMETRIC_SUCCESS &&
+ biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG) !=
+ BiometricManager.BIOMETRIC_SUCCESS) {
+ return true
+ }
+
+ return false
+}
+
+private fun containsBiometricAuthenticatorWithDeviceCredentials(
+ allowedAuthenticators: Int
+): Boolean {
+ val allowedAuthContainsDeviceCredential = (allowedAuthenticators ==
+ Authenticators.BIOMETRIC_WEAK or Authenticators.DEVICE_CREDENTIAL) ||
+ (allowedAuthenticators ==
+ Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL)
+ return allowedAuthContainsDeviceCredential
+}
+
/**
* Sets up the biometric prompt with the UI specific bits.
* // TODO(b/333445112) : Pass in opId once dependency is confirmed via CryptoObject
@@ -236,56 +307,41 @@
biometricDisplayInfo: BiometricDisplayInfo,
openMoreOptionsPage: () -> Unit,
biometricRequestInfo: BiometricRequestInfo,
- biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit
): BiometricPrompt {
- val finalAuthenticators = removeDeviceCredential(biometricRequestInfo.allowedAuthenticators)
+ val listener =
+ DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> openMoreOptionsPage() }
- val biometricPrompt = BiometricPrompt.Builder(context)
+ val promptContentViewBuilder = PromptContentViewWithMoreOptionsButton.Builder()
+ .setMoreOptionsButtonListener(context.mainExecutor, listener)
+ biometricDisplayInfo.descriptionForCredential?.let {
+ promptContentViewBuilder.setDescription(it) }
+
+ val biometricPromptBuilder = BiometricPrompt.Builder(context)
.setTitle(biometricDisplayInfo.displayTitleText)
- // TODO(b/333445112) : Migrate to using new methods and strings recently aligned upon
- .setNegativeButton(context.getString(if (biometricFlowType == BiometricFlowType.GET)
- R.string
- .dropdown_presentation_more_sign_in_options_text else R.string.string_more_options),
- getMainExecutor(context)) { _, _ ->
- openMoreOptionsPage()
- }
- .setAllowedAuthenticators(finalAuthenticators)
+ .setAllowedAuthenticators(biometricRequestInfo.allowedAuthenticators)
.setConfirmationRequired(true)
.setLogoBitmap(biometricDisplayInfo.providerIcon)
.setLogoDescription(biometricDisplayInfo.providerName)
- .setDescription(biometricDisplayInfo.descriptionForCredential)
- .build()
+ .setContentView(promptContentViewBuilder.build())
- return biometricPrompt
-}
-
-// TODO(b/333445112) : Remove after larger level alignments made on fallback negative button
-// For the time being, we do not support the pin fallback until UX is decided.
-private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int {
- var finalAuthenticators = requestAllowedAuthenticators
-
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
- BiometricManager.Authenticators.BIOMETRIC_WEAK)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
+ if (!containsBiometricAuthenticatorWithDeviceCredentials(biometricDisplayInfo
+ .biometricRequestInfo.allowedAuthenticators)) {
+ biometricPromptBuilder.setNegativeButton(context.getString(R.string.string_cancel),
+ getMainExecutor(context)
+ ) { _: DialogInterface?, _: Int -> onCancelFlowAndFinish() }
}
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL or
- BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
+ biometricDisplayInfo.displaySubtitleText?.let { biometricPromptBuilder.setSubtitle(it) }
- if (requestAllowedAuthenticators == (BiometricManager.Authenticators.DEVICE_CREDENTIAL)) {
- finalAuthenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
- }
-
- return finalAuthenticators
+ return biometricPromptBuilder.build()
}
/**
* Sets up the biometric authentication callback.
*/
private fun setupBiometricAuthenticationCallback(
- sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult?, BiometricError?) -> Unit,
selectedEntry: EntryInfo,
onCancelFlowAndFinish: () -> Unit,
onIllegalStateAndFinish: (String) -> Unit,
@@ -301,7 +357,7 @@
try {
if (authResult != null) {
onBiometricPromptStateChange(BiometricPromptState.COMPLETE)
- sendDataToProvider(selectedEntry, authResult)
+ sendDataToProvider(selectedEntry, authResult, /*authError=*/null)
} else {
onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " +
"returned a null value.")
@@ -326,8 +382,10 @@
// into the selector, parity applies to the selector's cancellation instead
// of the provider's biometric prompt cancellation.
onCancelFlowAndFinish()
+ } else {
+ sendDataToProvider(selectedEntry, /*authResult=*/null, /*authError=*/
+ BiometricError(errorCode, errString))
}
- // TODO(b/333445772) : Propagate to provider
}
override fun onAuthenticationFailed() {
@@ -414,15 +472,29 @@
}
val singleEntryType = selectedEntry.credentialType
val username = selectedEntry.userName
+
+ // TODO(b/330396140) : Finalize localization and parsing for specific sign in option flows
+ // (fingerprint, face, etc...))
displayTitleText = context.getString(
generateDisplayTitleTextResCode(singleEntryType),
getRequestDisplayInfo.appName
)
+
descriptionText = context.getString(
- R.string.get_dialog_title_single_tap_for,
+ when (singleEntryType) {
+ CredentialType.PASSKEY ->
+ R.string.get_dialog_description_single_tap_passkey
+
+ CredentialType.PASSWORD ->
+ R.string.get_dialog_description_single_tap_password
+
+ CredentialType.UNKNOWN ->
+ R.string.get_dialog_description_single_tap_saved_sign_in
+ },
getRequestDisplayInfo.appName,
username
)
+
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
@@ -448,23 +520,12 @@
getCreateTitleResCode(createRequestDisplayInfo),
createRequestDisplayInfo.appName
)
- val descriptionText: String = context.getString(
- when (createRequestDisplayInfo.type) {
- CredentialType.PASSKEY ->
- R.string.choose_create_single_tap_passkey_title
- CredentialType.PASSWORD ->
- R.string.choose_create_single_tap_password_title
-
- CredentialType.UNKNOWN ->
- R.string.choose_create_single_tap_sign_in_title
- },
- createRequestDisplayInfo.appName,
- )
- // TODO(b/333445112) : Add a subtitle and any other recently aligned ideas
+ // TODO(b/330396140) : If footerDescription is null, determine if we need to fallback
return BiometricDisplayInfo(providerIcon = icon, providerName = providerName,
- displayTitleText = displayTitleText, descriptionForCredential = descriptionText,
- biometricRequestInfo = selectedEntry.biometricRequest as BiometricRequestInfo)
+ displayTitleText = displayTitleText, descriptionForCredential = selectedEntry
+ .footerDescription, biometricRequestInfo = selectedEntry.biometricRequest
+ as BiometricRequestInfo, displaySubtitleText = createRequestDisplayInfo.title)
}
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index 7e7a74f..3c80113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -22,7 +22,9 @@
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
- const val BIOMETRIC_AUTH_TYPE = "BIOMETRIC_AUTH_TYPE"
- const val BIOMETRIC_AUTH_FAILURE = "BIOMETRIC_AUTH_FAILURE"
+ // TODO(b/333445772) : Qualify error codes fully for propagation
+ const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT"
+ const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE"
+ const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE"
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index d13d86f..149c14a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -349,6 +349,38 @@
}
}
+@Composable
+fun MoreOptionTopAppBarWithCustomNavigation(
+ text: String,
+ onNavigationIconClicked: () -> Unit,
+ navigationIcon: ImageVector,
+ navigationIconContentDescription: String,
+ bottomPadding: Dp,
+) {
+ Row(
+ modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ IconButton(
+ modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
+ onClick = onNavigationIconClicked
+ ) {
+ Box(
+ modifier = Modifier.size(48.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = navigationIcon,
+ contentDescription = navigationIconContentDescription,
+ modifier = Modifier.size(24.dp).autoMirrored(),
+ tint = LocalAndroidColorScheme.current.onSurfaceVariant,
+ )
+ }
+ }
+ LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+ }
+}
+
private fun Modifier.autoMirrored() = composed {
when (LocalLayoutDirection.current) {
LayoutDirection.Rtl -> graphicsLayer(scaleX = -1f)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 122b896..a0915d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -46,6 +46,7 @@
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.model.EntryInfo
@@ -581,7 +582,11 @@
onMoreOptionSelected: () -> Unit,
requestDisplayInfo: RequestDisplayInfo,
enabledProviderInfo: EnabledProviderInfo,
- onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit,
+ onBiometricEntrySelected: (
+ EntryInfo,
+ BiometricPrompt.AuthenticationResult?,
+ BiometricError?
+ ) -> Unit,
onCancelFlowAndFinish: () -> Unit,
onIllegalScreenStateAndFinish: (String) -> Unit,
fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 72b7814..e68baf4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -32,6 +32,7 @@
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.material3.Divider
import androidx.compose.material3.TextButton
@@ -52,6 +53,7 @@
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BiometricError
import com.android.credentialmanager.common.BiometricFlowType
import com.android.credentialmanager.common.BiometricPromptState
import com.android.credentialmanager.common.ProviderActivityState
@@ -69,6 +71,7 @@
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
+import com.android.credentialmanager.common.ui.MoreOptionTopAppBarWithCustomNavigation
import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.common.ui.SnackbarActionText
@@ -147,7 +150,7 @@
.currentScreenState == GetScreenState.BIOMETRIC_SELECTION) {
BiometricSelectionPage(
biometricEntry = getCredentialUiState.activeEntry,
- onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+ onMoreOptionSelected = viewModel::getFlowOnMoreOptionOnlySelected,
onCancelFlowAndFinish = viewModel::onUserCancel,
onIllegalStateAndFinish = viewModel::onIllegalUiState,
requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
@@ -162,6 +165,28 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange
)
+ } else if (credmanBiometricApiEnabled() &&
+ getCredentialUiState.currentScreenState
+ == GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY) {
+ AllSignInOptionCard(
+ providerInfoList = getCredentialUiState.providerInfoList,
+ providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+ onEntrySelected = viewModel::getFlowOnEntrySelected,
+ onBackButtonClicked = viewModel::onUserCancel,
+ onCancel = viewModel::onUserCancel,
+ onLog = { viewModel.logUiEvent(it) },
+ customTopBar = { MoreOptionTopAppBarWithCustomNavigation(
+ text = stringResource(
+ R.string.get_dialog_title_sign_in_options),
+ onNavigationIconClicked = viewModel::onUserCancel,
+ navigationIcon = Icons.Filled.Close,
+ navigationIconContentDescription =
+ stringResource(R.string.accessibility_close_button),
+ bottomPadding = 0.dp
+ ) }
+ )
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS)
} else {
AllSignInOptionCard(
providerInfoList = getCredentialUiState.providerInfoList,
@@ -223,7 +248,11 @@
requestDisplayInfo: RequestDisplayInfo,
providerInfoList: List<ProviderInfo>,
providerDisplayInfo: ProviderDisplayInfo,
- onBiometricEntrySelected: (EntryInfo, BiometricPrompt.AuthenticationResult?) -> Unit,
+ onBiometricEntrySelected: (
+ EntryInfo,
+ BiometricPrompt.AuthenticationResult?,
+ BiometricError?
+ ) -> Unit,
fallbackToOriginalFlow: (BiometricFlowType) -> Unit,
getBiometricPromptState: () -> BiometricPromptState,
onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
@@ -637,7 +666,13 @@
return providerId
}
-/** Draws the secondary credential selection page, where all sign-in options are listed. */
+/**
+ * Draws the secondary credential selection page, where all sign-in options are listed.
+ *
+ * By default, this card has 'back' navigation whereby user can navigate back to invoke
+ * [onBackButtonClicked]. However if a different top bar with possibly a different navigation
+ * is required, then the caller of this Composable can set a [customTopBar].
+ */
@Composable
fun AllSignInOptionCard(
providerInfoList: List<ProviderInfo>,
@@ -646,16 +681,21 @@
onBackButtonClicked: () -> Unit,
onCancel: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
+ customTopBar: (@Composable() () -> Unit)? = null
) {
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
SheetContainerCard(topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(R.string.get_dialog_title_sign_in_options),
- onNavigationIconClicked = onBackButtonClicked,
- bottomPadding = 0.dp,
- )
+ if (customTopBar != null) {
+ customTopBar()
+ } else {
+ MoreOptionTopAppBar(
+ text = stringResource(R.string.get_dialog_title_sign_in_options),
+ onNavigationIconClicked = onBackButtonClicked,
+ bottomPadding = 0.dp,
+ )
+ }
}) {
var isFirstSection = true
// For username
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b03407b..8e78861 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -163,7 +163,11 @@
/** The single tap biometric selection page. */
BIOMETRIC_SELECTION,
- /** The secondary credential selection page, where all sign-in options are listed. */
+ /**
+ * The secondary credential selection page, where all sign-in options are listed.
+ *
+ * This state is expected to go back to PRIMARY_SELECTION on back navigation
+ */
ALL_SIGN_IN_OPTIONS,
/** The snackbar only page when there's no account but only a remoteEntry. */
@@ -171,6 +175,14 @@
/** The snackbar when there are only auth entries and all of them turn out to be empty. */
UNLOCKED_AUTH_ENTRIES_ONLY,
+
+ /**
+ * The secondary credential selection page, where all sign-in options are listed.
+ *
+ * This state has no option for the user to navigate back to PRIMARY_SELECTION, and
+ * instead can be terminated independently.
+ */
+ ALL_SIGN_IN_OPTIONS_ONLY,
}
@@ -285,7 +297,7 @@
providerDisplayInfo.remoteEntry != null)
GetScreenState.REMOTE_ONLY
else if (isRequestForAllOptions)
- GetScreenState.ALL_SIGN_IN_OPTIONS
+ GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY
else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo)))
GetScreenState.BIOMETRIC_SELECTION
else GetScreenState.PRIMARY_SELECTION
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
index 6c14563..c6013e2 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorUiStateGetMapperTest.kt
@@ -100,7 +100,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = true)
assertThat(getCredentialUiState).isEqualTo(
@@ -113,7 +112,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(listOf(passkeyCredentialEntryInfo,
unknownCredentialEntryInfo)))).toGet(isPrimary = true)
@@ -133,7 +131,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1))).toGet(isPrimary = false)
assertThat(getCredentialUiState).isEqualTo(
@@ -152,7 +149,6 @@
val getCredentialUiState = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = listOf(createProviderInfo(credentialList1),
createProviderInfo(credentialList2))).toGet(isPrimary = false)
diff --git a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
index b79f34c..cf839f8 100644
--- a/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
+++ b/packages/CredentialManager/wear/robotests/src/com/android/credentialmanager/CredentialSelectorViewModelTest.kt
@@ -121,7 +121,6 @@
stateFlow.value = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = emptyList())
mViewModel.back()
@@ -136,7 +135,6 @@
stateFlow.value = Request.Get(
token = null,
resultReceiver = null,
- finalResponseReceiver = null,
providerInfos = emptyList())
mViewModel.back()
diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml
index 4428dbb..3cc64a6 100644
--- a/packages/PrintSpooler/res/values-night/themes.xml
+++ b/packages/PrintSpooler/res/values-night/themes.xml
@@ -30,6 +30,7 @@
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index 4dcad10..bd96025 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -31,6 +31,7 @@
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34c60a1..9faebe2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -381,6 +381,14 @@
});
}
+ /** Gets devices with matched connection states. */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(states);
+ }
+
public boolean isEnabled(BluetoothDevice device) {
if (mService == null || device == null) {
return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 6fb0179..e91c0bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -168,17 +168,16 @@
/**
* Gets a list of preferences that other apps have injected.
*
- * @param profileId Identifier of the user/profile to obtain the injected settings for or
- * UserHandle.USER_CURRENT for all profiles associated with current user.
+ * @param profiles UserHandles of the users/profiles for which to obtain the injected settings.
*/
public Map<Integer, List<Preference>> getInjectedSettings(Context prefContext,
- final int profileId) {
+ final Set<UserHandle> profiles) {
final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final List<UserHandle> profiles = um.getUserProfiles();
+ final List<UserHandle> allProfilesForUser = um.getUserProfiles();
final ArrayMap<Integer, List<Preference>> result = new ArrayMap<>();
mSettings.clear();
- for (UserHandle userHandle : profiles) {
- if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+ for (UserHandle userHandle : allProfilesForUser) {
+ if (profiles.contains(userHandle)) {
final List<Preference> prefs = new ArrayList<>();
Iterable<InjectedSetting> settings = getSettings(userHandle);
for (InjectedSetting setting : settings) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 9b1e4b7..3e29872 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -52,6 +52,7 @@
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
import android.os.Build;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -131,6 +132,7 @@
protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@NonNull protected final Context mContext;
@NonNull protected final String mPackageName;
+ @NonNull protected final UserHandle mUserHandle;
private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
@@ -140,16 +142,19 @@
/* package */ InfoMediaManager(
@NonNull Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
@NonNull LocalBluetoothManager localBluetoothManager) {
mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
+ mUserHandle = userHandle;
}
/** Creates an instance of InfoMediaManager. */
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
+ @Nullable UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -159,16 +164,23 @@
packageName = context.getPackageName();
}
+ if (userHandle == null) {
+ userHandle = android.os.Process.myUserHandle();
+ }
+
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
- return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
+ return new RouterInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
- return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
+ return new NoOpInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
}
} else {
- return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
+ return new ManagerInfoMediaManager(
+ context, packageName, userHandle, localBluetoothManager);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 63056b6..0c2414c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,7 +138,10 @@
}
mInfoMediaManager =
- InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
+ // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The
+ // package name is not sufficient to unambiguously identify an app.
+ InfoMediaManager.createInstance(
+ context, packageName, /* userHandle */ null, mLocalBluetoothManager);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 23063da..d621751 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -21,6 +21,7 @@
import android.media.MediaRouter2Manager;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -53,8 +54,9 @@
/* package */ ManagerInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
@@ -87,8 +89,7 @@
@Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
- // TODO: b/279555229 - provide real user handle of a caller.
- mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
+ mRouterManager.transfer(mPackageName, route, mUserHandle);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index cf11c6d..d2b018c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -20,6 +20,7 @@
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -58,8 +59,9 @@
NoOpInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 0dceeba..045c60d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -25,7 +25,7 @@
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
-import android.os.Process;
+import android.os.UserHandle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -70,15 +70,16 @@
/* package */ RouterInfoMediaManager(
Context context,
@NonNull String packageName,
+ @NonNull UserHandle userHandle,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
- super(context, packageName, localBluetoothManager);
+ super(context, packageName, userHandle, localBluetoothManager);
MediaRouter2 router = null;
if (Flags.enableCrossUserRoutingInMediaRouter2()) {
try {
- router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+ router = MediaRouter2.getInstance(context, packageName, userHandle);
} catch (IllegalArgumentException ex) {
// Do nothing
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index f0185b9..3bd37a2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,21 +64,23 @@
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+ InfoMediaManager.createInstance(
+ mContext, mContext.getPackageName(), mContext.getUser(), null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
+ InfoMediaManager manager =
+ InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -86,7 +88,8 @@
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
+ InfoMediaManager.createInstance(
+ mContext, mContext.getPackageName(), mContext.getUser(), null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
new file mode 100644
index 0000000..384fd9b
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/OWNERS
@@ -0,0 +1,3 @@
+#Android Media Better Together
+ivanbuper@google.com
+aquilescanta@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index d793867..a4b87da 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -144,7 +144,8 @@
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(
+ mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
index d630301..908f50d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -46,6 +46,7 @@
new NoOpInfoMediaManager(
mContext,
/* packageName */ "FAKE_PACKAGE_NAME",
+ mContext.getUser(),
/* localBluetoothManager */ null);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index ad3eb92..e77cf2f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -531,13 +531,22 @@
pw.println(" put NAMESPACE KEY VALUE [default]");
pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE.");
pw.println(" {default} to set as the default value.");
+ pw.println(" override NAMESPACE KEY VALUE");
+ pw.println(" Set flag NAMESPACE/KEY to the given VALUE, and ignores "
+ + "server-updates for");
+ pw.println(" this flag. This can still be called even if there is no underlying "
+ + "value set.");
pw.println(" delete NAMESPACE KEY");
pw.println(" Delete the entry for KEY for the given NAMESPACE.");
+ pw.println(" clear_override NAMESPACE KEY");
+ pw.println(" Clear local sticky flag override for KEY in the given NAMESPACE.");
pw.println(" list_namespaces [--public]");
pw.println(" Prints the name of all (or just the public) namespaces.");
pw.println(" list [NAMESPACE]");
pw.println(" Print all keys and values defined, optionally for the given "
+ "NAMESPACE.");
+ pw.println(" list_local_overrides");
+ pw.println(" Print all flags that have been overridden.");
pw.println(" reset RESET_MODE [NAMESPACE]");
pw.println(" Reset all flag values, optionally for a NAMESPACE, according to "
+ "RESET_MODE.");
@@ -547,8 +556,9 @@
+ "flags are reset");
pw.println(" set_sync_disabled_for_tests SYNC_DISABLED_MODE");
pw.println(" Modifies bulk property setting behavior for tests. When in one of the"
- + " disabled modes this ensures that config isn't overwritten.");
- pw.println(" SYNC_DISABLED_MODE is one of:");
+ + " disabled modes");
+ pw.println(" this ensures that config isn't overwritten. SYNC_DISABLED_MODE is "
+ + "one of:");
pw.println(" none: Sync is not disabled. A reboot may be required to restart"
+ " syncing.");
pw.println(" persistent: Sync is disabled, this state will survive a reboot.");
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 40db52e..c88c373 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -117,6 +117,7 @@
"SystemUILogLib",
"SystemUIPluginLib",
"SystemUISharedLib",
+ "SystemUI-shared-utils",
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
@@ -263,6 +264,7 @@
"SystemUISharedLib",
"SystemUICustomizationLib",
"SystemUICustomizationTestUtils",
+ "SystemUI-shared-utils",
"SystemUI-statsd",
"SettingsLib",
"com_android_systemui_flags_lib",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt
new file mode 100644
index 0000000..c2a2696
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/UnfoldModifiers.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.fold.ui.composable
+
+import androidx.annotation.FloatRange
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.modifiers.padding
+import kotlin.math.roundToInt
+
+/**
+ * Applies a translation that feeds off of the unfold transition that's active while the device is
+ * being folded or unfolded, effectively shifting the element towards the fold hinge.
+ *
+ * @param startSide `true` if the affected element is on the start side (left-hand side in
+ * left-to-right layouts), `false` otherwise.
+ * @param fullTranslation The maximum translation to apply when the element is the most shifted. The
+ * modifier will never apply more than this much translation on the element.
+ * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
+ * be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
+ */
+@Composable
+fun Modifier.unfoldTranslation(
+ startSide: Boolean,
+ fullTranslation: Dp,
+ @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
+): Modifier {
+ val translateToTheRight = startSide && LocalLayoutDirection.current == LayoutDirection.Ltr
+ return this.graphicsLayer {
+ translationX =
+ fullTranslation.toPx() *
+ if (translateToTheRight) {
+ 1 - unfoldProgress()
+ } else {
+ unfoldProgress() - 1
+ }
+ }
+}
+
+/**
+ * Applies horizontal padding that feeds off of the unfold transition that's active while the device
+ * is being folded or unfolded, effectively "squishing" the element on both sides.
+ *
+ * This is horizontal padding so it's applied on both the start and end sides of the element.
+ *
+ * @param fullPadding The maximum padding to apply when the element is the most padded. The modifier
+ * will never apply more than this much horizontal padding on the element.
+ * @param unfoldProgress A provider for the amount of progress of the unfold transition. This should
+ * be sourced from the `UnfoldTransitionInteractor`, ideally through a view-model.
+ */
+@Composable
+fun Modifier.unfoldHorizontalPadding(
+ fullPadding: Dp,
+ @FloatRange(from = 0.0, to = 1.0) unfoldProgress: () -> Float,
+): Modifier {
+ return this.padding(
+ horizontal = { (fullPadding.toPx() * (1 - unfoldProgress())).roundToInt() },
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 02a12e4..c6c6f57 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -407,15 +407,16 @@
AndroidView(
factory = { context ->
ModernShadeCarrierGroupMobileView.constructAndBind(
- context = context,
- logger = viewModel.mobileIconsViewModel.logger,
- slot = "mobile_carrier_shade_group",
- viewModel =
- (viewModel.mobileIconsViewModel.viewModelForSub(
- subId,
- StatusBarLocation.SHADE_CARRIER_GROUP
- ) as ShadeCarrierGroupMobileIconViewModel),
- )
+ context = context,
+ logger = viewModel.mobileIconsViewModel.logger,
+ slot = "mobile_carrier_shade_group",
+ viewModel =
+ (viewModel.mobileIconsViewModel.viewModelForSub(
+ subId,
+ StatusBarLocation.SHADE_CARRIER_GROUP
+ ) as ShadeCarrierGroupMobileIconViewModel),
+ )
+ .also { it.setOnClickListener { viewModel.onShadeCarrierGroupClicked() } }
},
)
}
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 9bd6f81..01c27a4d 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
@@ -65,6 +65,8 @@
import com.android.compose.modifiers.thenIf
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.fold.ui.composable.unfoldHorizontalPadding
+import com.android.systemui.fold.ui.composable.unfoldTranslation
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.controller.MediaHierarchyManager
@@ -289,6 +291,7 @@
remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) }
val tileSquishiness by
animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
+ val unfoldTransitionProgress by viewModel.unfoldTransitionProgress.collectAsState()
val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
val density = LocalDensity.current
@@ -337,10 +340,23 @@
modifier =
Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
.then(brightnessMirrorShowingModifier)
+ .unfoldHorizontalPadding(
+ fullPadding = dimensionResource(R.dimen.notification_side_paddings),
+ ) {
+ unfoldTransitionProgress
+ }
)
Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
- Box(modifier = Modifier.weight(1f)) {
+ Box(
+ modifier =
+ Modifier.weight(1f).unfoldTranslation(
+ startSide = true,
+ fullTranslation = dimensionResource(R.dimen.notification_side_paddings),
+ ) {
+ unfoldTransitionProgress
+ },
+ ) {
BrightnessMirror(
viewModel = viewModel.brightnessMirrorViewModel,
qsSceneAdapter = viewModel.qsSceneAdapter,
@@ -407,7 +423,16 @@
Modifier.weight(1f)
.fillMaxHeight()
.padding(bottom = navBarBottomHeight)
- .then(brightnessMirrorShowingModifier),
+ .then(brightnessMirrorShowingModifier)
+ .unfoldTranslation(
+ startSide = false,
+ fullTranslation =
+ dimensionResource(
+ R.dimen.notification_side_paddings,
+ ),
+ ) {
+ unfoldTransitionProgress
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
index fc511e1..e15d315 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -69,9 +69,9 @@
role = Role.Button
contentDescription = label
},
- color = MaterialTheme.colorScheme.primaryContainer,
+ color = MaterialTheme.colorScheme.tertiaryContainer,
shape = RoundedCornerShape(28.dp),
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
onClick = onClick,
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 780e3f2..b2351c4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -66,8 +66,8 @@
val colors =
if (viewModel.isChecked) {
ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.primaryContainer,
- contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
)
} else {
ButtonDefaults.buttonColors(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
index c743314..51e2064 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -30,9 +30,11 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
@@ -119,6 +121,7 @@
) {
for (itemIndex in items.indices) {
val item = items[itemIndex]
+ val isSelected = itemIndex == scope.selectedIndex
Row(
modifier =
Modifier.height(48.dp)
@@ -126,7 +129,7 @@
.semantics {
item.contentDescription?.let { contentDescription = it }
role = Role.Switch
- selected = itemIndex == scope.selectedIndex
+ selected = isSelected
}
.clickable(
interactionSource = null,
@@ -137,7 +140,11 @@
verticalAlignment = Alignment.CenterVertically,
) {
if (item.icon !== Empty) {
- with(items[itemIndex]) { icon() }
+ CompositionLocalProvider(
+ LocalContentColor provides colors.getIconColor(isSelected)
+ ) {
+ with(items[itemIndex]) { icon() }
+ }
}
}
}
@@ -163,7 +170,10 @@
) {
val item = items[itemIndex]
if (item.icon !== Empty) {
- with(items[itemIndex]) { label() }
+ val textColor = colors.getLabelColor(itemIndex == scope.selectedIndex)
+ CompositionLocalProvider(LocalContentColor provides textColor) {
+ with(items[itemIndex]) { label() }
+ }
}
}
}
@@ -265,8 +275,22 @@
val indicatorColor: Color,
/** Color of the indicator background. */
val indicatorBackgroundColor: Color,
+ /** Color of the icon. */
+ val iconColor: Color,
+ /** Color of the icon when it's selected. */
+ val selectedIconColor: Color,
+ /** Color of the label. */
+ val labelColor: Color,
+ /** Color of the label when it's selected. */
+ val selectedLabelColor: Color,
)
+private fun VolumePanelRadioButtonBarColors.getIconColor(selected: Boolean): Color =
+ if (selected) selectedIconColor else iconColor
+
+private fun VolumePanelRadioButtonBarColors.getLabelColor(selected: Boolean): Color =
+ if (selected) selectedLabelColor else labelColor
+
object VolumePanelRadioButtonBarDefaults {
val DefaultIndicatorBackgroundPadding = 8.dp
@@ -283,12 +307,20 @@
*/
@Composable
fun defaultColors(
- indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+ indicatorColor: Color = MaterialTheme.colorScheme.tertiaryContainer,
indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ iconColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedIconColor: Color = MaterialTheme.colorScheme.onTertiaryContainer,
+ labelColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ selectedLabelColor: Color = MaterialTheme.colorScheme.onSurface,
): VolumePanelRadioButtonBarColors =
VolumePanelRadioButtonBarColors(
indicatorColor = indicatorColor,
indicatorBackgroundColor = indicatorBackgroundColor,
+ iconColor = iconColor,
+ selectedIconColor = selectedIconColor,
+ labelColor = labelColor,
+ selectedLabelColor = selectedLabelColor,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index 9a98bde..f377fa6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
import androidx.compose.foundation.basicMarquee
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -29,7 +30,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.toColor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
@@ -88,18 +88,13 @@
isSelected = buttonViewModel.button.isChecked,
onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
contentDescription = label,
- icon = {
- Icon(
- icon = buttonViewModel.button.icon,
- tint = buttonViewModel.iconColor.toColor(),
- )
- },
+ icon = { Icon(icon = buttonViewModel.button.icon) },
label = {
Text(
modifier = Modifier.basicMarquee(),
text = label,
style = MaterialTheme.typography.labelMedium,
- color = buttonViewModel.labelColor.toColor(),
+ color = LocalContentColor.current,
textAlign = TextAlign.Center,
maxLines = 2
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index a54d005..a3467f2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -107,7 +107,7 @@
}
}
transition.AnimatedVisibility(
- visible = { it },
+ visible = { it || !isExpandable },
enter =
expandVertically(animationSpec = tween(durationMillis = EXPAND_DURATION_MILLIS)),
exit =
@@ -122,7 +122,7 @@
val sliderState by sliderViewModel.slider.collectAsState()
transition.AnimatedVisibility(
modifier = Modifier.padding(top = 16.dp),
- visible = { it },
+ visible = { it || !isExpandable },
enter = enterTransition(index = index, totalCount = viewModels.size),
exit = exitTransition(index = index, totalCount = viewModels.size)
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 228d292..9f5ab3c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -61,7 +61,8 @@
modifier =
modifier.clearAndSetSemantics {
if (!state.isEnabled) disabled()
- contentDescription = state.label
+ contentDescription =
+ state.disabledMessage?.let { "${state.label}, $it" } ?: state.label
// provide a not animated value to the a11y because it fails to announce the
// settled value when it changes rapidly.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 2d4b63e..ae9794a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -50,6 +50,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
@@ -65,8 +66,6 @@
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.FakeSceneDataSource
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -87,7 +86,6 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.GlobalSettings
import com.google.common.truth.Truth
-import dagger.Lazy
import java.util.Optional
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -171,7 +169,7 @@
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var deviceEntryInteractor: DeviceEntryInteractor
- @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
private lateinit var fakeSceneDataSource: FakeSceneDataSource
@@ -217,9 +215,13 @@
)
mSetFlagsRule.disableFlags(
FLAG_SIDEFPS_CONTROLLER_REFACTOR,
- AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
- AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
)
+ if (!com.android.systemui.Flags.sceneContainer()) {
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ AConfigFlags.FLAG_REFACTOR_KEYGUARD_DISMISS_INTENT,
+ )
+ }
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -268,7 +270,6 @@
falsingManager,
userSwitcherController,
featureFlags,
- kosmos.sceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -283,7 +284,7 @@
deviceProvisionedController,
faceAuthAccessibilityDelegate,
keyguardTransitionInteractor,
- primaryBouncerInteractor,
+ { primaryBouncerInteractor },
) {
deviceEntryInteractor
}
@@ -804,17 +805,17 @@
}
@Test
+ @EnableSceneContainer
fun dismissesKeyguard_whenSceneChangesToGone() =
kosmos.testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
// Upon init, we have never dismisses the keyguard.
underTest.onInit()
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Once the view is attached, we start listening but simply going to the bouncer scene
- // is
- // not enough to trigger a dismissal of the keyguard.
+ // is not enough to trigger a dismissal of the keyguard.
underTest.onViewAttached()
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Bouncer, "reason")
@@ -830,7 +831,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While listening, going from the bouncer scene to the gone scene, does dismiss the
// keyguard.
@@ -852,11 +854,11 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While listening, moving back to the bouncer scene does not dismiss the keyguard
// again.
- clearInvocations(viewMediatorCallback)
+ clearInvocations(primaryBouncerInteractor)
fakeSceneDataSource.pause()
sceneInteractor.changeScene(Scenes.Bouncer, "reason")
sceneTransitionStateFlow.value =
@@ -871,7 +873,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Bouncer)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Bouncer)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Detaching the view stops listening, so moving from the bouncer scene to the gone
// scene
@@ -891,7 +894,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// While not listening, moving to the lockscreen does not dismiss the keyguard.
fakeSceneDataSource.pause()
@@ -908,7 +912,8 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Lockscreen)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
runCurrent()
- verify(viewMediatorCallback, never()).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor, never())
+ .notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
// Reattaching the view starts listening again so moving from the bouncer scene to the
// gone scene now does dismiss the keyguard again, this time from lockscreen.
@@ -927,7 +932,7 @@
fakeSceneDataSource.unpause(expectedScene = Scenes.Gone)
sceneTransitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone)
runCurrent()
- verify(viewMediatorCallback).keyguardDone(anyInt())
+ verify(primaryBouncerInteractor).notifyKeyguardAuthenticatedPrimaryAuth(anyInt())
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 9f52ae9..04c4efb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -45,9 +45,9 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -88,7 +88,7 @@
FlingAnimationUtils mFlingAnimationUtilsClosing;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
BouncerSwipeTouchHandler mTouchHandler;
@@ -258,7 +258,7 @@
}
private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
- DreamTouchHandler.TouchSession touchSession,
+ TouchHandler.TouchSession touchSession,
NotificationShadeWindowController notificationShadeWindowController) {
touchHandler.onSessionStart(touchSession);
verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
@@ -677,8 +677,8 @@
@Test
public void testTouchSessionOnRemovedCalledTwice() {
mTouchHandler.onSessionStart(mTouchSession);
- ArgumentCaptor<DreamTouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.Callback.class);
+ ArgumentCaptor<TouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.Callback.class);
verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
onRemovedCallbackCaptor.getValue().onRemoved();
onRemovedCallbackCaptor.getValue().onRemoved();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
index 6aa821f..27bffd0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/ShadeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java
@@ -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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -52,7 +52,7 @@
ShadeViewController mShadeViewController;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
ShadeTouchHandler mTouchHandler;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
index 7cdd478..099771c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimControllerTest.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
index ebbcf98..82de50c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/scrim/ScrimManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/scrim/ScrimManagerTest.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import static com.google.common.truth.Truth.assertThat;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index caf9219..1cd9d76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -32,7 +32,6 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.testKosmos
@@ -86,7 +85,6 @@
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
backgroundDispatcher = kosmos.testDispatcher,
- flags = kosmos.sceneContainerFlags,
clock = clock,
getSecurityMode = getSecurityMode,
userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 85774c6..60b48f2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -571,7 +571,7 @@
// THEN the view layout is never updated
verify(windowManager, never()).updateViewLayout(any(), any())
- // CLEANUPL we hide to end the job that listens for the finishedGoingToSleep signal
+ // CLEANUP we hide to end the job that listens for the finishedGoingToSleep signal
controllerOverlay.hide()
}
}
@@ -595,7 +595,7 @@
controllerOverlay.updateOverlayParams(overlayParams)
// THEN the view layout is updated
- verify(windowManager, never()).updateViewLayout(any(), any())
+ verify(windowManager).updateViewLayout(any(), any())
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 741cde8..d850f17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -29,10 +29,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
@@ -56,6 +56,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@@ -75,7 +76,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- kosmos.fakeSceneContainerFlags.enabled = true
mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index b0d03b1..361b078 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -18,6 +18,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -25,14 +26,15 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -48,11 +50,13 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val authenticationInteractor = kosmos.authenticationInteractor
+ private val uiEventLoggerFake = kosmos.uiEventLoggerFake
private lateinit var underTest: BouncerInteractor
@@ -83,6 +87,7 @@
// Thus, when auth method is sim, we expect to skip here.
assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
.isEqualTo(AuthenticationResult.SKIPPED)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
}
@Test
@@ -104,6 +109,8 @@
// Wrong 6-digit pin
assertThat(underTest.authenticate(listOf(1, 2, 3, 5, 5, 6), tryAutoConfirm = true))
.isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Correct input.
assertThat(
@@ -113,6 +120,9 @@
)
)
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
@@ -148,6 +158,8 @@
// Wrong input.
assertThat(underTest.authenticate("alohamora".toList()))
.isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Too short input.
assertThat(
@@ -165,6 +177,9 @@
// Correct input.
assertThat(underTest.authenticate("password".toList()))
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
@@ -187,6 +202,8 @@
assertThat(wrongPattern.size)
.isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
+ assertThat(uiEventLoggerFake[0].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_FAILURE.id)
// Too short input.
val tooShortPattern =
@@ -200,6 +217,9 @@
// Correct input.
assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
.isEqualTo(AuthenticationResult.SUCCEEDED)
+ assertThat(uiEventLoggerFake[1].eventId)
+ .isEqualTo(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS.id)
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 0db0e07..b83c0ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -34,10 +34,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -60,6 +60,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class BouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -70,7 +71,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.bouncerViewModel
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 5bb36a0..256687b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -389,6 +389,61 @@
assertThat(isAnimationEnabled).isTrue()
}
+ @Test
+ fun onPinButtonClicked_whenInputSameLengthAsHintedPin_ignoresClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isEqualTo(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH - 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+ kosmos.fakeAuthenticationRepository.pauseCredentialChecking()
+ // If credential checking were not paused, this would check the credentials and succeed.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH)
+ runCurrent()
+
+ // This one should be ignored because the user has already entered a number of digits
+ // that's equal to the length of the hinting PIN length. It should result in a PIN
+ // that's exactly the same length as the hinting PIN length.
+ underTest.onPinButtonClicked(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ runCurrent()
+
+ assertThat(pin)
+ .isEqualTo(
+ buildList {
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH) { index ->
+ add(index + 1)
+ }
+ }
+ )
+
+ kosmos.fakeAuthenticationRepository.unpauseCredentialChecking()
+ runCurrent()
+ assertThat(pin).isEmpty()
+ }
+
+ @Test
+ fun onPinButtonClicked_whenPinNotHinted_doesNotIgnoreClick() =
+ testScope.runTest {
+ val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
+ val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+ assertThat(hintedPinLength).isNull()
+ lockDeviceAndOpenPinBouncer()
+
+ repeat(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) { repetition ->
+ underTest.onPinButtonClicked(repetition + 1)
+ runCurrent()
+ }
+
+ assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1)
+ }
+
private fun TestScope.switchToScene(toScene: SceneKey) {
val currentScene by collectLastValue(sceneInteractor.currentScene)
val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index f21e969..497180b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -61,7 +62,6 @@
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.fakeUserTracker
@@ -698,10 +698,9 @@
}
@Test
+ @EnableSceneContainer
fun isCommunalShowing_whenSceneContainerEnabled() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
-
// Verify default is false
val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 5caf35b..37a6ac6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -38,6 +38,7 @@
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.TrustAgentDisabled
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UnattendedUpdate
import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReason.UserLockdown
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.fakeSystemPropertiesHelper
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
@@ -47,7 +48,6 @@
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -62,6 +62,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class DeviceEntryInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -74,7 +75,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.deviceEntryInteractor
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 2af6566..41bc1dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -39,10 +39,10 @@
import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.complication.ComplicationHostViewController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
import com.android.systemui.statusbar.BlurUtils;
import org.junit.Before;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index c143468..4e18a47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -50,11 +50,12 @@
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchMonitor;
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.complication.ComplicationLayoutEngine;
import com.android.systemui.dreams.complication.HideComplicationTouchHandler;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -127,6 +128,12 @@
DreamOverlayComponent mDreamOverlayComponent;
@Mock
+ AmbientTouchComponent.Factory mAmbientTouchComponentFactory;
+
+ @Mock
+ AmbientTouchComponent mAmbientTouchComponent;
+
+ @Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
@@ -136,7 +143,7 @@
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+ TouchMonitor mTouchMonitor;
@Mock
DreamOverlayStateController mStateController;
@@ -166,8 +173,6 @@
.thenReturn(mDreamOverlayContainerViewController);
when(mLifecycleOwner.getRegistry())
.thenReturn(mLifecycleRegistry);
- when(mDreamOverlayComponent.getDreamOverlayTouchMonitor())
- .thenReturn(mDreamOverlayTouchMonitor);
when(mComplicationComponentFactory
.create(any(), any(), any(), any()))
.thenReturn(mComplicationComponent);
@@ -179,8 +184,11 @@
.create(any(), any()))
.thenReturn(mDreamComplicationComponent);
when(mDreamOverlayComponentFactory
- .create(any(), any(), any(), any()))
+ .create(any(), any(), any()))
.thenReturn(mDreamOverlayComponent);
+ when(mAmbientTouchComponentFactory.create(any(), any())).thenReturn(mAmbientTouchComponent);
+ when(mAmbientTouchComponent.getTouchMonitor())
+ .thenReturn(mTouchMonitor);
when(mDreamOverlayContainerViewController.getContainerView())
.thenReturn(mDreamOverlayContainerView);
@@ -193,6 +201,7 @@
mComplicationComponentFactory,
mDreamComplicationComponentFactory,
mDreamOverlayComponentFactory,
+ mAmbientTouchComponentFactory,
mStateController,
mKeyguardUpdateMonitor,
mUiEventLogger,
@@ -486,6 +495,7 @@
assertThat(mService.shouldShowComplications()).isFalse();
clearInvocations(mDreamOverlayComponent);
+ clearInvocations(mAmbientTouchComponent);
clearInvocations(mWindowManager);
// New dream starting with dream complications showing. Note that when a new dream is
@@ -505,7 +515,7 @@
// Verify that new instances of overlay container view controller and overlay touch monitor
// are created.
verify(mDreamOverlayComponent).getDreamOverlayContainerViewController();
- verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor();
+ verify(mAmbientTouchComponent).getTouchMonitor();
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
index a434158..8481586 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/complication/HideComplicationTouchHandlerTest.java
@@ -35,9 +35,9 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.complication.Complication;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
@@ -74,7 +74,7 @@
MotionEvent mMotionEvent;
@Mock
- DreamTouchHandler.TouchSession mSession;
+ TouchHandler.TouchSession mSession;
@Mock
DreamOverlayStateController mStateController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
index 8dcf903..29fbee0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -54,7 +55,7 @@
@Mock
CentralSurfaces mCentralSurfaces;
@Mock
- DreamTouchHandler.TouchSession mTouchSession;
+ TouchHandler.TouchSession mTouchSession;
CommunalTouchHandler mTouchHandler;
@Mock
Lifecycle mLifecycle;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 1dd5d07..12f8918 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -37,8 +38,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -76,7 +75,6 @@
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = kosmos.sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -249,9 +247,9 @@
}
@Test
+ @EnableSceneContainer
fun animationDozingTransitions() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
val isAnimate by collectLastValue(underTest.animateDozingTransitions)
underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 33eb90a..f685058 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -172,12 +173,147 @@
val recommendationsLoadingModel =
SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
- underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+ underTest.setRecommendationsLoadingState(recommendationsLoadingModel)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
+ @Test
+ fun addMediaControlPlayingThenRemote() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId = InstanceId.fakeInstanceId(123)
+ val remoteInstanceId = InstanceId.fakeInstanceId(321)
+ val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId)
+ val remoteData = createMediaData("app2", true, REMOTE, false, remoteInstanceId)
+
+ underTest.addSelectedUserMediaEntry(playingData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId))
+ underTest.addSelectedUserMediaEntry(remoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId),
+ MediaCommonModel.MediaControl(remoteInstanceId)
+ )
+ }
+
+ @Test
+ fun switchMediaControlsPlaying() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val playingInstanceId1 = InstanceId.fakeInstanceId(123)
+ val playingInstanceId2 = InstanceId.fakeInstanceId(321)
+ var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1)
+ var playingData2 = createMediaData("app2", false, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId1),
+ MediaCommonModel.MediaControl(playingInstanceId2)
+ )
+ .inOrder()
+
+ playingData1 = createMediaData("app1", false, LOCAL, false, playingInstanceId1)
+ playingData2 = createMediaData("app2", true, LOCAL, false, playingInstanceId2)
+
+ underTest.addSelectedUserMediaEntry(playingData1)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
+ underTest.addSelectedUserMediaEntry(playingData2)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
+
+ assertThat(sortedMedia?.size).isEqualTo(2)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(playingInstanceId2),
+ MediaCommonModel.MediaControl(playingInstanceId1)
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun fullOrderTest() =
+ testScope.runTest {
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
+ val instanceId1 = InstanceId.fakeInstanceId(123)
+ val instanceId2 = InstanceId.fakeInstanceId(456)
+ val instanceId3 = InstanceId.fakeInstanceId(321)
+ val instanceId4 = InstanceId.fakeInstanceId(654)
+ val instanceId5 = InstanceId.fakeInstanceId(124)
+ val playingAndLocalData = createMediaData("app1", true, LOCAL, false, instanceId1)
+ val playingAndRemoteData = createMediaData("app2", true, REMOTE, false, instanceId2)
+ val stoppedAndLocalData = createMediaData("app3", false, LOCAL, false, instanceId3)
+ val stoppedAndRemoteData = createMediaData("app4", false, REMOTE, false, instanceId4)
+ val canResumeData = createMediaData("app5", false, LOCAL, true, instanceId5)
+
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ val mediaRecommendations =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+
+ underTest.addSelectedUserMediaEntry(stoppedAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId3))
+
+ underTest.addSelectedUserMediaEntry(stoppedAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId4))
+
+ underTest.addSelectedUserMediaEntry(canResumeData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId5))
+
+ underTest.addSelectedUserMediaEntry(playingAndLocalData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1))
+
+ underTest.addSelectedUserMediaEntry(playingAndRemoteData)
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2))
+
+ underTest.setRecommendation(mediaRecommendations)
+ underTest.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
+
+ assertThat(sortedMedia?.size).isEqualTo(6)
+ assertThat(sortedMedia?.values)
+ .containsExactly(
+ MediaCommonModel.MediaControl(instanceId1),
+ MediaCommonModel.MediaControl(instanceId2),
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(instanceId4),
+ MediaCommonModel.MediaControl(instanceId3),
+ MediaCommonModel.MediaControl(instanceId5),
+ )
+ .inOrder()
+ }
+
+ private fun createMediaData(
+ app: String,
+ playing: Boolean,
+ playbackLocation: Int,
+ isResume: Boolean,
+ instanceId: InstanceId,
+ ): MediaData {
+ return MediaData(
+ playbackLocation = playbackLocation,
+ resumption = isResume,
+ notificationKey = "key: $app",
+ isPlaying = playing,
+ instanceId = instanceId
+ )
+ }
+
companion object {
+ private const val LOCAL = MediaData.PLAYBACK_LOCAL
+ private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index a0a1eb3..c15776e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -94,22 +95,29 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
val userMedia = MediaData(active = false)
val instanceId = userMedia.instanceId
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isTrue()
+ assertThat(sortedMedia).containsExactly(MediaCommonModel.MediaControl(instanceId))
assertThat(mediaFilterRepository.removeSelectedUserMediaEntry(instanceId, userMedia))
.isTrue()
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isFalse()
assertThat(hasActiveMedia).isFalse()
assertThat(hasAnyMedia).isFalse()
+ assertThat(sortedMedia).isEmpty()
}
@Test
@@ -119,6 +127,7 @@
collectLastValue(underTest.hasActiveMediaOrRecommendation)
val hasAnyMediaOrRecommendation by
collectLastValue(underTest.hasAnyMediaOrRecommendation)
+ val sortedMedia by collectLastValue(underTest.sortedMedia)
kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false)
val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
@@ -131,14 +140,28 @@
val userMedia = MediaData(active = false)
mediaFilterRepository.setRecommendation(userMediaRecommendation)
+ mediaFilterRepository.setRecommendationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE))
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(userMedia.instanceId)
+ )
assertThat(hasActiveMediaOrRecommendation).isTrue()
assertThat(hasAnyMediaOrRecommendation).isTrue()
+ assertThat(sortedMedia)
+ .containsExactly(
+ MediaCommonModel.MediaRecommendations(KEY_MEDIA_SMARTSPACE),
+ MediaCommonModel.MediaControl(userMedia.instanceId)
+ )
+ .inOrder()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index 1cba185..d9224d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -187,7 +187,12 @@
underTest.startMediaOutputDialog(expandable, PACKAGE_NAME)
verify(kosmos.mediaOutputDialogManager)
- .createAndShowWithController(eq(PACKAGE_NAME), eq(true), eq(dialogTransitionController))
+ .createAndShowWithController(
+ eq(PACKAGE_NAME),
+ eq(true),
+ eq(dialogTransitionController),
+ eq(null)
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
new file mode 100644
index 0000000..2e5fde8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileDataInteractorTest : SysuiTestCase() {
+
+ private val isAvailable = true
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val reduceBrightColorsController = kosmos.reduceBrightColorsController
+ private val underTest: ReduceBrightColorsTileDataInteractor =
+ ReduceBrightColorsTileDataInteractor(
+ testScope.testScheduler,
+ isAvailable,
+ reduceBrightColorsController
+ )
+
+ @Test
+ fun alwaysAvailable() =
+ testScope.runTest {
+ val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+ assertThat(availability).hasSize(1)
+ assertThat(availability.last()).isEqualTo(isAvailable)
+ }
+
+ @Test
+ fun dataMatchesTheRepository() =
+ testScope.runTest {
+ val dataList: List<ReduceBrightColorsTileModel> by
+ collectValues(
+ underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ runCurrent()
+
+ reduceBrightColorsController.isReduceBrightColorsActivated = true
+ runCurrent()
+
+ reduceBrightColorsController.isReduceBrightColorsActivated = false
+ runCurrent()
+
+ assertThat(dataList).hasSize(3)
+ assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+ }
+
+ private companion object {
+ val TEST_USER = UserHandle.of(1)!!
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..6ea5e63
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.reduceBrightColorsController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = kosmos.reduceBrightColorsController
+
+ private val underTest =
+ ReduceBrightColorsTileUserActionInteractor(
+ inputHandler,
+ controller,
+ )
+
+ @Test
+ fun handleClickWhenEnabled() = runTest {
+ val wasEnabled = true
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleClickWhenDisabled() = runTest {
+ val wasEnabled = false
+ controller.isReduceBrightColorsActivated = wasEnabled
+
+ underTest.handleInput(QSTileInputTestKtx.click(ReduceBrightColorsTileModel(wasEnabled)))
+
+ assertThat(controller.isReduceBrightColorsActivated).isEqualTo(!wasEnabled)
+ }
+
+ @Test
+ fun handleLongClickWhenDisabled() = runTest {
+ val enabled = false
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenEnabled() = runTest {
+ val enabled = true
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(ReduceBrightColorsTileModel(enabled)))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ assertThat(it.intent.action).isEqualTo(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
new file mode 100644
index 0000000..10e9bd6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.tiles.impl.reducebrightness.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.service.quicksettings.Tile
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.qsReduceBrightColorsTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val config = kosmos.qsReduceBrightColorsTileConfig
+
+ private lateinit var mapper: ReduceBrightColorsTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ ReduceBrightColorsTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_extra_dim_icon_on, TestStubDrawable())
+ addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
+ }
+ .resources,
+ context.theme
+ )
+ }
+
+ @Test
+ fun disabledModel() {
+ val inputModel = ReduceBrightColorsTileModel(false)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState =
+ createReduceBrightColorsTileState(
+ QSTileState.ActivationState.INACTIVE,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun enabledModel() {
+ val inputModel = ReduceBrightColorsTileModel(true)
+
+ val outputState = mapper.map(config, inputModel)
+
+ val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.ACTIVE)
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createReduceBrightColorsTileState(
+ activationState: QSTileState.ActivationState,
+ ): QSTileState {
+ val label =
+ context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+ return QSTileState(
+ {
+ Icon.Loaded(
+ context.getDrawable(
+ if (activationState == QSTileState.ActivationState.ACTIVE)
+ R.drawable.qs_extra_dim_icon_on
+ else R.drawable.qs_extra_dim_icon_off
+ )!!,
+ null
+ )
+ },
+ label,
+ activationState,
+ context.resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[
+ if (activationState == QSTileState.ActivationState.ACTIVE) Tile.STATE_ACTIVE
+ else Tile.STATE_INACTIVE],
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ null,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
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 139289a..3727c11 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
@@ -24,8 +24,8 @@
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -34,18 +34,8 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -64,8 +54,6 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
private val footerActionsViewModel = mock<FooterActionsViewModel>()
private val footerActionsViewModelFactory =
@@ -74,45 +62,18 @@
}
private val footerActionsController = mock<FooterActionsController>()
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
private val sceneInteractor = kosmos.sceneInteractor
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
private lateinit var underTest: QuickSettingsSceneViewModel
@Before
fun setUp() {
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
+ kosmos.fakeFeatureFlagsClassic.set(Flags.NEW_NETWORK_SLICE_UI, false)
underTest =
QuickSettingsSceneViewModel(
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
footerActionsViewModelFactory = footerActionsViewModelFactory,
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 93302e3..65fd101 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -51,6 +51,7 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -69,30 +70,22 @@
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -136,9 +129,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
+@EnableSceneContainer
class SceneFrameworkIntegrationTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -180,25 +174,6 @@
)
}
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags = kosmos.fakeFeatureFlagsClassic,
- scope = testScope.backgroundScope,
- )
-
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
@@ -241,23 +216,11 @@
bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
bouncerViewModel = kosmos.bouncerViewModel
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
-
shadeSceneViewModel =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -266,6 +229,7 @@
footerActionsController = kosmos.footerActionsController,
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
sceneInteractor = sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
val displayTracker = FakeDisplayTracker(context)
@@ -275,15 +239,15 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ bouncerInteractor = bouncerInteractor,
keyguardInteractor = keyguardInteractor,
- flags = kosmos.fakeSceneContainerFlags,
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
falsingCollector = kosmos.falsingCollector,
falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
- bouncerInteractor = bouncerInteractor,
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
@@ -292,7 +256,6 @@
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
shadeInteractor = kosmos.shadeInteractor,
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 7f7c24e..8e2eea1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -23,10 +23,10 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -39,9 +39,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneContainerRepositoryTest : SysuiTestCase() {
- private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index b179c30..63f4816 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -23,13 +23,13 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
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.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -55,7 +56,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest = kosmos.sceneInteractor
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index d5e43f4..bfe5ef7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -31,7 +31,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -82,7 +81,6 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 61adcd2..1472a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -54,7 +54,6 @@
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -102,7 +101,6 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val sceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
@@ -124,15 +122,15 @@
applicationScope = testScope.backgroundScope,
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
+ bouncerInteractor = bouncerInteractor,
keyguardInteractor = keyguardInteractor,
- flags = sceneContainerFlags,
sysUiState = sysUiState,
displayId = Display.DEFAULT_DISPLAY,
sceneLogger = mock(),
falsingCollector = falsingCollector,
falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
- bouncerInteractor = bouncerInteractor,
simBouncerInteractor = { kosmos.simBouncerInteractor },
authenticationInteractor = { authenticationInteractor },
windowController = windowController,
@@ -141,7 +139,6 @@
headsUpInteractor = kosmos.headsUpNotificationInteractor,
occlusionInteractor = kosmos.sceneContainerOcclusionInteractor,
faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor,
shadeInteractor = kosmos.shadeInteractor,
)
}
@@ -1245,7 +1242,6 @@
"Cannot start on the Gone scene and have the device be locked at the same time."
}
- sceneContainerFlags.enabled = true
kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
authenticationMethod?.let {
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 2938acf..ae5bf07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -21,7 +21,6 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.kosmos.Kosmos
import com.google.common.truth.Truth
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,15 +33,11 @@
@DisableSceneContainer
fun isNotEnabled_withoutAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(false)
- Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(false)
- Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(false)
}
@Test
@EnableSceneContainer
fun isEnabled_withAconfigFlags() {
Truth.assertThat(SceneContainerFlag.isEnabled).isEqualTo(true)
- Truth.assertThat(SceneContainerFlagsImpl().isEnabled()).isEqualTo(true)
- Truth.assertThat(Kosmos().sceneContainerFlags.isEnabled()).isEqualTo(true)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 7b0127e..427b66b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -23,13 +23,13 @@
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
@@ -45,6 +45,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -58,7 +59,6 @@
@Before
fun setUp() {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index cbbcce9..420418b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -22,6 +22,7 @@
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -30,7 +31,6 @@
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -52,6 +52,7 @@
@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class ShadeControllerSceneImplTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
@@ -64,7 +65,6 @@
@Before
fun setup() {
kosmos.testCase = this
- kosmos.fakeSceneContainerFlags.enabled = true
kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index e759b50..26f342a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -22,9 +22,9 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.recents.utilities.Utilities
import com.android.systemui.testKosmos
@@ -43,8 +43,9 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@Ignore("b/328827631")
+@EnableSceneContainer
class ShadeBackActionInteractorImplTest : SysuiTestCase() {
- val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ val kosmos = testKosmos()
val testScope = kosmos.testScope
val sceneInteractor = kosmos.sceneInteractor
val underTest = kosmos.shadeBackActionInteractor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 4c573d3..f89f18a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -2,29 +2,18 @@
import android.content.Intent
import android.provider.AlarmClock
+import android.provider.Settings
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
-import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argThat
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -40,43 +29,13 @@
class ShadeHeaderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val mobileIconsInteractor = kosmos.fakeMobileIconsInteractor
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
-
- private lateinit var underTest: ShadeHeaderViewModel
+ private val underTest: ShadeHeaderViewModel = kosmos.shadeHeaderViewModel
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
- underTest =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
}
@Test
@@ -105,6 +64,19 @@
)
}
+ @Test
+ fun onShadeCarrierGroupClicked_launchesNetworkSettings() =
+ testScope.runTest {
+ val activityStarter = kosmos.activityStarter
+ underTest.onShadeCarrierGroupClicked()
+
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcherAction(Settings.ACTION_WIRELESS_SETTINGS)),
+ anyInt(),
+ )
+ }
+
companion object {
private val SUB_1 =
SubscriptionModel(
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 7a681b3..2727af6 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
@@ -27,8 +27,6 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -41,20 +39,13 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.privacyChipInteractor
-import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.startable.shadeStartable
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
import com.android.systemui.testKosmos
+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
@@ -79,29 +70,8 @@
private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor }
private val shadeRepository by lazy { kosmos.shadeRepository }
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
- private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
-
- private var mobileIconsViewModel: MobileIconsViewModel =
- MobileIconsViewModel(
- logger = mock(),
- verboseLogger = mock(),
- interactor = mobileIconsInteractor,
- airplaneModeInteractor =
- AirplaneModeInteractor(
- FakeAirplaneModeRepository(),
- FakeConnectivityRepository(),
- FakeMobileConnectionsRepository(),
- ),
- constants = mock(),
- flags,
- scope = testScope.backgroundScope,
- )
-
private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
- private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
-
private lateinit var underTest: ShadeSceneViewModel
@Mock private lateinit var mediaDataManager: MediaDataManager
@@ -109,23 +79,12 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- shadeHeaderViewModel =
- ShadeHeaderViewModel(
- applicationScope = testScope.backgroundScope,
- context = context,
- shadeInteractor = kosmos.shadeInteractor,
- mobileIconsInteractor = mobileIconsInteractor,
- mobileIconsViewModel = mobileIconsViewModel,
- privacyChipInteractor = kosmos.privacyChipInteractor,
- clockInteractor = kosmos.shadeHeaderClockInteractor,
- broadcastDispatcher = fakeBroadcastDispatcher,
- )
underTest =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
- shadeHeaderViewModel = shadeHeaderViewModel,
+ shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
qsSceneAdapter = qsSceneAdapter,
notifications = kosmos.notificationsPlaceholderViewModel,
brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
@@ -134,6 +93,7 @@
footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
footerActionsController = kosmos.footerActionsController,
sceneInteractor = kosmos.sceneInteractor,
+ unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
)
}
@@ -297,4 +257,26 @@
shadeRepository.setShadeMode(ShadeMode.Split)
assertThat(shadeMode).isEqualTo(ShadeMode.Split)
}
+
+ @Test
+ fun unfoldTransitionProgress() =
+ testScope.runTest {
+ val unfoldProvider = kosmos.fakeUnfoldTransitionProgressProvider
+ val progress by collectLastValue(underTest.unfoldTransitionProgress)
+
+ unfoldProvider.onTransitionStarted()
+ assertThat(progress).isEqualTo(1f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(progress).isEqualTo(transitionProgress)
+ }
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(progress).isEqualTo(1f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(progress).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index a3cf929..01e1aa59 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -23,11 +23,11 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -46,11 +46,11 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
- fakeSceneContainerFlags.enabled = true
fakeFeatureFlagsClassic.apply {
set(Flags.FULL_SCREEN_USER_SWITCHER, false)
set(Flags.NSSL_DEBUG_LINES, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 8f7a56d..a023033 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -52,7 +52,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -128,7 +127,7 @@
@Before
fun setUp() {
- assertThat(kosmos.sceneContainerFlags.isEnabled()).isEqualTo(SceneContainerFlag.isEnabled)
+ assertThat(SceneContainerFlag.isEnabled).isEqualTo(SceneContainerFlag.isEnabled)
overrideResource(R.bool.config_use_split_notification_shade, false)
movementFlow = MutableStateFlow(BurnInModel())
whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
similarity index 60%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
index 6a801e0..3b4cce4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractorTest.kt
@@ -15,42 +15,31 @@
*/
package com.android.systemui.unfold.domain.interactor
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
-import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.google.common.truth.Truth.assertThat
-import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
-open class UnfoldTransitionInteractorTest : SysuiTestCase() {
+@RunWith(AndroidJUnit4::class)
+class UnfoldTransitionInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val unfoldTransitionProgressProvider = kosmos.fakeUnfoldTransitionProgressProvider
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
- private val unfoldTransitionRepository =
- UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
-
- private lateinit var underTest: UnfoldTransitionInteractor
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest = UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
- }
+ private val underTest: UnfoldTransitionInteractor = kosmos.unfoldTransitionInteractor
@Test
fun waitForTransitionFinish_noEvents_doesNotComplete() =
@@ -88,4 +77,26 @@
assertThat(deferred.isCompleted).isFalse()
deferred.cancel()
}
+
+ @Test
+ fun unfoldProgress() =
+ testScope.runTest {
+ val progress by collectLastValue(underTest.unfoldProgress)
+ runCurrent()
+
+ unfoldTransitionProgressProvider.onTransitionStarted()
+ assertThat(progress).isEqualTo(1f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldTransitionProgressProvider.onTransitionProgress(transitionProgress)
+ assertThat(progress).isEqualTo(transitionProgress)
+ }
+
+ unfoldTransitionProgressProvider.onTransitionFinishing()
+ assertThat(progress).isEqualTo(1f)
+
+ unfoldTransitionProgressProvider.onTransitionFinished()
+ assertThat(progress).isEqualTo(1f)
+ }
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 8e2bd9b..79bf5f1 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -267,6 +267,9 @@
/** True if the clock will react to tone changes in the seed color. */
val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is large frame clock, which will use weather in compose. */
+ val useCustomClockScene: Boolean = false,
)
/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
@@ -283,6 +286,9 @@
* animation will be used (e.g. a simple translation).
*/
val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+ /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
+ val useCustomClockScene: Boolean = false,
)
/** Structure for keeping clock-specific settings */
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
new file mode 100644
index 0000000..63600be
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:color="@color/overlay_button_ripple">
+ <item android:id="@android:id/background">
+ <shape android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSecondary"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
similarity index 67%
rename from packages/SystemUI/res/layout/auth_biometric_icon.xml
rename to packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index b2df63d..bb8cece 100644
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ 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.
@@ -14,13 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<com.airbnb.lottie.LottieAnimationView
+<shape
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/biometric_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitXY"/>
\ No newline at end of file
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
new file mode 100644
index 0000000..a5b44e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android = "http://schemas.android.com/apk/res/android">
+ <size
+ android:width = "@dimen/overlay_action_chip_margin_start"
+ android:height = "0dp"/>
+</shape>
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
similarity index 67%
copy from packages/SystemUI/res/layout/auth_biometric_icon.xml
copy to packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
index b2df63d..76779f9 100644
--- a/packages/SystemUI/res/layout/auth_biometric_icon.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_container_clipping_shape.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ 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.
@@ -14,13 +14,10 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<com.airbnb.lottie.LottieAnimationView
+<shape
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/biometric_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitXY"/>
\ No newline at end of file
+ android:shape="rectangle">
+ <!-- We don't actually draw anything, just expressing the shape for clipping. -->
+ <solid android:color="#0000"/>
+ <corners android:radius="10000dp"/> <!-- fully-rounded radius -->
+</shape>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 43ff7b6..76d10cc 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -256,6 +256,24 @@
app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />
<Button
+ android:id="@+id/audio_sharing_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="9dp"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/barrier"
+ app:layout_constraintVertical_bias="1"
+ android:visibility="gone" />
+
+ <Button
android:id="@+id/done_button"
style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 1eb05bf..e3c5a7d 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -36,8 +36,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
- android:focusable="true"
- android:accessibilityTraversalBefore="@android:id/edit"
+ android:focusable="false"
+ android:importantForAccessibility="yes"
android:clipToPadding="false"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index eeb64bd8..6a5b999f 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -20,39 +20,37 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <ImageView
+ <FrameLayout
android:id="@+id/actions_container_background"
android:visibility="gone"
- android:layout_height="0dp"
- android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
android:elevation="4dp"
- android:background="@drawable/action_chip_container_background"
- android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:background="@drawable/shelf_action_chip_container_background"
+ android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal"
android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="@+id/actions_container"
- app:layout_constraintEnd_toEndOf="@+id/actions_container"
- app:layout_constraintBottom_toTopOf="@id/guideline"/>
- <HorizontalScrollView
- android:id="@+id/actions_container"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
- android:paddingHorizontal="@dimen/overlay_action_container_padding_end"
- android:paddingVertical="@dimen/overlay_action_container_padding_vertical"
- android:elevation="4dp"
- android:scrollbars="none"
- app:layout_constraintHorizontal_bias="0"
- app:layout_constraintWidth_percent="1.0"
- app:layout_constraintWidth_max="wrap"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintBottom_toBottomOf="@id/actions_container_background">
- <LinearLayout
- android:id="@+id/screenshot_actions"
+ app:layout_constraintBottom_toTopOf="@id/guideline"
+ >
+ <HorizontalScrollView
+ android:id="@+id/actions_container"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
- </HorizontalScrollView>
+ android:layout_height="wrap_content"
+ android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical"
+ android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start"
+ android:background="@drawable/shelf_action_container_clipping_shape"
+ android:clipToOutline="true"
+ android:scrollbars="none">
+ <LinearLayout
+ android:id="@+id/screenshot_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:showDividers="middle"
+ android:divider="@drawable/shelf_action_chip_divider"
+ android:animateLayoutChanges="true"
+ />
+ </HorizontalScrollView>
+ </FrameLayout>
<View
android:id="@+id/screenshot_preview_border"
android:layout_width="0dp"
@@ -66,7 +64,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview"
- app:layout_constraintBottom_toTopOf="@id/actions_container"/>
+ app:layout_constraintBottom_toTopOf="@id/actions_container_background"/>
<ImageView
android:id="@+id/screenshot_preview"
android:layout_width="@dimen/overlay_x_scale"
diff --git a/packages/SystemUI/res/layout/shelf_action_chip.xml b/packages/SystemUI/res/layout/shelf_action_chip.xml
new file mode 100644
index 0000000..c7606e4
--- /dev/null
+++ b/packages/SystemUI/res/layout/shelf_action_chip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@style/FloatingOverlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingVertical="@dimen/overlay_action_chip_padding_vertical"
+ android:gravity="center"
+ android:background="@drawable/shelf_action_chip_background"
+ >
+ <ImageView
+ android:id="@+id/overlay_action_chip_icon"
+ android:tint="?androidprv:attr/materialColorOnSecondary"
+ android:layout_width="@dimen/overlay_action_chip_icon_size"
+ android:layout_height="@dimen/overlay_action_chip_icon_size"/>
+ <TextView
+ android:id="@+id/overlay_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:textSize="@dimen/overlay_action_chip_text_size"
+ android:textColor="?androidprv:attr/materialColorOnSecondary"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2fed457..f60f6c7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -678,6 +678,8 @@
<string name="turn_on_bluetooth">Use Bluetooth</string>
<!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_connected">Connected</string>
+ <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
<!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_saved">Saved</string>
<!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -690,6 +692,10 @@
<string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
<!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
<string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+ <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
+ <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
<!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c509356..e8e1cab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -90,7 +90,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -134,7 +134,6 @@
private final UserSwitcherController mUserSwitcherController;
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
- private final SceneContainerFlags mSceneContainerFlags;
private final SessionTracker mSessionTracker;
private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
@@ -456,7 +455,6 @@
FalsingManager falsingManager,
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags,
GlobalSettings globalSettings,
SessionTracker sessionTracker,
Optional<SideFpsController> sideFpsController,
@@ -491,7 +489,6 @@
mFalsingManager = falsingManager;
mUserSwitcherController = userSwitcherController;
mFeatureFlags = featureFlags;
- mSceneContainerFlags = sceneContainerFlags;
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
if (SideFpsControllerRefactor.isEnabled()) {
@@ -534,7 +531,7 @@
showPrimarySecurityScreen(false);
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
// When the scene framework says that the lockscreen has been dismissed, dismiss the
// keyguard here, revealing the underlying app or launcher:
mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4987724..8c51a4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -225,7 +225,7 @@
protected static final int BIOMETRIC_STATE_STOPPED = 0;
/** Biometric authentication state: Listening. */
- protected static final int BIOMETRIC_STATE_RUNNING = 1;
+ private static final int BIOMETRIC_STATE_RUNNING = 1;
/**
* Biometric authentication: Cancelling and waiting for the relevant biometric service to
@@ -1145,6 +1145,7 @@
if (getUserCanSkipBouncer(userId)) {
mTrustManager.unlockedByBiometricForUser(userId, FACE);
}
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
mLogger.d("onFaceAuthenticated");
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1155,12 +1156,6 @@
}
}
- // Intentionally update the fingerprint running state after sending the
- // onBiometricAuthenticated callback to listeners. Updating the fingerprint listening state
- // can update the state of the device which listeners to the callback may rely on.
- // For example, the alternate bouncer visibility state or udfps finger down state.
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-
// Only authenticate face once when assistant is visible
mAssistantVisible = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 9b09265..afeb0f8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -189,6 +189,5 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
@Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer,
- KeyguardBypassController bypassController);
+ View notificationContainer);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
index 4e5df35..cf2675b 100644
--- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java
@@ -74,7 +74,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -131,7 +131,6 @@
@NonNull private final KeyguardInteractor mKeyguardInteractor;
@NonNull private final View.AccessibilityDelegate mAccessibilityDelegate;
@NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor;
- @NonNull private final SceneContainerFlags mSceneContainerFlags;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -208,8 +207,7 @@
@NonNull FeatureFlags featureFlags,
PrimaryBouncerInteractor primaryBouncerInteractor,
Context context,
- Lazy<DeviceEntryInteractor> deviceEntryInteractor,
- SceneContainerFlags sceneContainerFlags
+ Lazy<DeviceEntryInteractor> deviceEntryInteractor
) {
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -236,7 +234,6 @@
mResources = resources;
mContext = context;
mDeviceEntryInteractor = deviceEntryInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mAccessibilityDelegate = new View.AccessibilityDelegate() {
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
@@ -746,7 +743,7 @@
// play device entry haptic (consistent with UDFPS controller longpress)
vibrateOnLongPress();
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractor.get().attemptDeviceEntry();
} else {
mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
index 7e96e48..615363d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationAnimationController.java
@@ -72,6 +72,7 @@
private boolean mEndAnimationCanceled = false;
@MagnificationState
private int mState = STATE_DISABLED;
+ private Runnable mOnAnimationEndRunnable;
WindowMagnificationAnimationController(@UiContext Context context) {
this(context, newValueAnimator(context.getResources()));
@@ -303,12 +304,7 @@
return;
}
- // If the animation is playing backwards, mStartSpec will be the final spec we would
- // like to reach.
- AnimationSpec spec = isReverse ? mStartSpec : mEndSpec;
- mController.updateWindowMagnificationInternal(
- spec.mScale, spec.mCenterX, spec.mCenterY,
- mMagnificationFrameOffsetRatioX, mMagnificationFrameOffsetRatioY);
+ mOnAnimationEndRunnable.run();
if (mState == STATE_DISABLING) {
mController.deleteWindowMagnification();
@@ -333,6 +329,10 @@
public void onAnimationRepeat(Animator animation) {
}
+ void setOnAnimationEndRunnable(Runnable runnable) {
+ mOnAnimationEndRunnable = runnable;
+ }
+
private void sendAnimationCallback(boolean success) {
if (mAnimationCallback != null) {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index a847c3d..9837e36 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -260,6 +260,11 @@
mContext = context;
mHandler = handler;
mAnimationController = animationController;
+ mAnimationController.setOnAnimationEndRunnable(() -> {
+ if (Flags.createWindowlessWindowMagnifier()) {
+ notifySourceBoundsChanged();
+ }
+ });
mAnimationController.setWindowMagnificationController(this);
mWindowMagnifierCallback = callback;
mSysUiState = sysUiState;
@@ -1051,11 +1056,15 @@
// Notify source bounds change when the magnifier is not animating.
if (!mAnimationController.isAnimating()) {
- mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ notifySourceBoundsChanged();
}
}
}
+ private void notifySourceBoundsChanged() {
+ mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds);
+ }
+
/**
* Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based
* on the position and size of {@link #mMagnificationFrame}.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 7cb028a..b8ff0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.qs
+import com.android.systemui.Flags
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -40,9 +41,14 @@
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor
import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.impl.reducebrightness.ui.ReduceBrightColorsTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -105,6 +111,7 @@
const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
const val COLOR_INVERSION_TILE_SPEC = "inversion"
const val FONT_SCALING_TILE_SPEC = "font_scaling"
+ const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness"
@Provides
@IntoMap
@@ -198,5 +205,41 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+ fun provideReduceBrightColorsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_extra_dim_icon_on,
+ labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /**
+ * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden
+ * behind a flag.
+ */
+ @Provides
+ @IntoMap
+ @StringKey(REDUCE_BRIGHTNESS_TILE_SPEC)
+ fun provideReduceBrightColorsTileViewModel(
+ factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>,
+ mapper: ReduceBrightColorsTileMapper,
+ stateInteractor: ReduceBrightColorsTileDataInteractor,
+ userActionInteractor: ReduceBrightColorsTileUserActionInteractor
+ ): QSTileViewModel =
+ if (Flags.qsNewTilesFuture())
+ factory.create(
+ TileSpec.create(REDUCE_BRIGHTNESS_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ else StubQSTileViewModel
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
new file mode 100644
index 0000000..ea00398
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.ambient.dagger
+
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent
+import dagger.Module
+
+@Module(subcomponents = [AmbientTouchComponent::class, InputSessionComponent::class])
+interface AmbientModule {
+ companion object {
+ const val TOUCH_HANDLERS = "touch_handlers"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index 66d413a..d0f08f5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
-
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
-import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
+package com.android.systemui.ambient.touch;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -38,9 +33,10 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Flags;
+import com.android.systemui.ambient.touch.dagger.BouncerSwipeModule;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -55,7 +51,7 @@
/**
* Monitor for tracking touches on the DreamOverlay to bring up the bouncer.
*/
-public class BouncerSwipeTouchHandler implements DreamTouchHandler {
+public class BouncerSwipeTouchHandler implements TouchHandler {
/**
* An interface for creating ValueAnimators.
*/
@@ -226,12 +222,12 @@
VelocityTrackerFactory velocityTrackerFactory,
LockPatternUtils lockPatternUtils,
UserTracker userTracker,
- @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING)
FlingAnimationUtils flingAnimationUtils,
- @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
FlingAnimationUtils flingAnimationUtilsClosing,
- @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
- @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
+ @Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+ @Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
UiEventLogger uiEventLogger) {
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
index cddba04..6a76c87 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/InputSession.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/InputSession.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
import android.os.Looper;
import android.view.Choreographer;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index e0bf52e..9ef9938 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -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.
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
+import static com.android.systemui.ambient.touch.dagger.ShadeModule.NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT;
import android.graphics.Rect;
import android.graphics.Region;
@@ -35,7 +35,7 @@
* {@link ShadeTouchHandler} is responsible for handling swipe down gestures over dream
* to bring down the shade.
*/
-public class ShadeTouchHandler implements DreamTouchHandler {
+public class ShadeTouchHandler implements TouchHandler {
private final Optional<CentralSurfaces> mSurfaces;
private final ShadeViewController mShadeViewController;
private final int mInitiationHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
index 1ec0008..190bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import android.graphics.Rect;
import android.graphics.Region;
@@ -25,24 +25,30 @@
import com.google.common.util.concurrent.ListenableFuture;
/**
- * The {@link DreamTouchHandler} interface provides a way for dream overlay components to observe
+ * The {@link TouchHandler} interface provides a way for dream overlay components to observe
* touch events and gestures with the ability to intercept the latter. Touch interaction sequences
* are abstracted as sessions. A session represents the time of first
- * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link DreamTouchHandler}
+ * {@code android.view.MotionEvent.ACTION_DOWN} event to the last {@link TouchHandler}
* stopping interception of gestures. If no gesture is intercepted, the session continues
- * indefinitely. {@link DreamTouchHandler} have the ability to create a stack of sessions, which
+ * indefinitely. {@link TouchHandler} have the ability to create a stack of sessions, which
* allows for motion logic to be captured in modal states.
*/
-public interface DreamTouchHandler {
+public interface TouchHandler {
/**
- * A touch session captures the interaction surface of a {@link DreamTouchHandler}. Clients
+ * A touch session captures the interaction surface of a {@link TouchHandler}. Clients
* register listeners as desired to participate in motion/gesture callbacks.
*/
interface TouchSession {
interface Callback {
+ /**
+ * Invoked when the session has been removed.
+ */
void onRemoved();
}
+ /**
+ * Registers a callback to be notified when there are updates to the {@link TouchSession}.
+ */
void registerCallback(Callback callback);
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 3b22b31..e7e12ba 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -37,10 +37,10 @@
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.util.display.DisplayHelper;
@@ -58,12 +58,12 @@
import javax.inject.Inject;
/**
- * {@link DreamOverlayTouchMonitor} is responsible for monitoring touches and gestures over the
+ * {@link TouchMonitor} is responsible for monitoring touches and gestures over the
* dream overlay and redirecting them to a set of listeners. This monitor is in charge of figuring
* out when listeners are eligible for receiving touches and filtering the listener pool if
* touches are consumed.
*/
-public class DreamOverlayTouchMonitor {
+public class TouchMonitor {
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
public String TAG = "DreamOverlayTouchMonitor";
@@ -83,11 +83,10 @@
};
-
/**
* Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
*/
- private ListenableFuture<DreamTouchHandler.TouchSession> push(
+ private ListenableFuture<TouchHandler.TouchSession> push(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
mMainExecutor.execute(() -> {
@@ -110,7 +109,7 @@
/**
* Removes a {@link TouchSessionImpl} from receiving further updates.
*/
- private ListenableFuture<DreamTouchHandler.TouchSession> pop(
+ private ListenableFuture<TouchHandler.TouchSession> pop(
TouchSessionImpl touchSessionImpl) {
return CallbackToFutureAdapter.getFuture(completer -> {
mMainExecutor.execute(() -> {
@@ -140,11 +139,11 @@
}
/**
- * {@link TouchSessionImpl} implements {@link DreamTouchHandler.TouchSession} for
- * {@link DreamOverlayTouchMonitor}. It enables the monitor to access the associated listeners
+ * {@link TouchSessionImpl} implements {@link TouchHandler.TouchSession} for
+ * {@link TouchMonitor}. It enables the monitor to access the associated listeners
* and provides the associated client with access to the monitor.
*/
- private static class TouchSessionImpl implements DreamTouchHandler.TouchSession {
+ private static class TouchSessionImpl implements TouchHandler.TouchSession {
private final HashSet<InputChannelCompat.InputEventListener> mEventListeners =
new HashSet<>();
private final HashSet<GestureDetector.OnGestureListener> mGestureListeners =
@@ -152,10 +151,10 @@
private final HashSet<Callback> mCallbacks = new HashSet<>();
private final TouchSessionImpl mPredecessor;
- private final DreamOverlayTouchMonitor mTouchMonitor;
+ private final TouchMonitor mTouchMonitor;
private final Rect mBounds;
- TouchSessionImpl(DreamOverlayTouchMonitor touchMonitor, Rect bounds,
+ TouchSessionImpl(TouchMonitor touchMonitor, Rect bounds,
TouchSessionImpl predecessor) {
mPredecessor = predecessor;
mTouchMonitor = touchMonitor;
@@ -179,12 +178,12 @@
}
@Override
- public ListenableFuture<DreamTouchHandler.TouchSession> push() {
+ public ListenableFuture<TouchHandler.TouchSession> push() {
return mTouchMonitor.push(this);
}
@Override
- public ListenableFuture<DreamTouchHandler.TouchSession> pop() {
+ public ListenableFuture<TouchHandler.TouchSession> pop() {
return mTouchMonitor.pop(this);
}
@@ -275,10 +274,10 @@
});
}
mCurrentInputSession = mInputSessionFactory.create(
- "dreamOverlay",
- mInputEventListener,
- mOnGestureListener,
- true)
+ "dreamOverlay",
+ mInputEventListener,
+ mOnGestureListener,
+ true)
.getInputSession();
}
@@ -323,21 +322,21 @@
private final HashSet<TouchSessionImpl> mActiveTouchSessions = new HashSet<>();
- private final Collection<DreamTouchHandler> mHandlers;
+ private final Collection<TouchHandler> mHandlers;
private final DisplayHelper mDisplayHelper;
private boolean mStopMonitoringPending;
private InputChannelCompat.InputEventListener mInputEventListener =
new InputChannelCompat.InputEventListener() {
- @Override
- public void onInputEvent(InputEvent ev) {
- // No Active sessions are receiving touches. Create sessions for each listener
- if (mActiveTouchSessions.isEmpty()) {
- final HashMap<DreamTouchHandler, DreamTouchHandler.TouchSession> sessionMap =
- new HashMap<>();
+ @Override
+ public void onInputEvent(InputEvent ev) {
+ // No Active sessions are receiving touches. Create sessions for each listener
+ if (mActiveTouchSessions.isEmpty()) {
+ final HashMap<TouchHandler, TouchHandler.TouchSession> sessionMap =
+ new HashMap<>();
- for (DreamTouchHandler handler : mHandlers) {
+ for (TouchHandler handler : mHandlers) {
if (!handler.isEnabled()) {
continue;
}
@@ -349,46 +348,49 @@
exclusionRect = getCurrentExclusionRect();
}
handler.getTouchInitiationRegion(
- maxBounds, initiationRegion, exclusionRect);
+ maxBounds, initiationRegion, exclusionRect);
- if (!initiationRegion.isEmpty()) {
- // Initiation regions require a motion event to determine pointer location
- // within the region.
- if (!(ev instanceof MotionEvent)) {
- continue;
+ if (!initiationRegion.isEmpty()) {
+ // Initiation regions require a motion event to determine pointer
+ // location
+ // within the region.
+ if (!(ev instanceof MotionEvent)) {
+ continue;
+ }
+
+ final MotionEvent motionEvent = (MotionEvent) ev;
+
+ // If the touch event is outside the region, then ignore.
+ if (!initiationRegion.contains(Math.round(motionEvent.getX()),
+ Math.round(motionEvent.getY()))) {
+ continue;
+ }
+ }
+
+ final TouchSessionImpl sessionStack = new TouchSessionImpl(
+ TouchMonitor.this, maxBounds, null);
+ mActiveTouchSessions.add(sessionStack);
+ sessionMap.put(handler, sessionStack);
}
- final MotionEvent motionEvent = (MotionEvent) ev;
-
- // If the touch event is outside the region, then ignore.
- if (!initiationRegion.contains(Math.round(motionEvent.getX()),
- Math.round(motionEvent.getY()))) {
- continue;
- }
+ // Informing handlers of new sessions is delayed until we have all
+ // created so the
+ // final session is correct.
+ sessionMap.forEach((dreamTouchHandler, touchSession)
+ -> dreamTouchHandler.onSessionStart(touchSession));
}
- final TouchSessionImpl sessionStack = new TouchSessionImpl(
- DreamOverlayTouchMonitor.this, maxBounds, null);
- mActiveTouchSessions.add(sessionStack);
- sessionMap.put(handler, sessionStack);
+ // Find active sessions and invoke on InputEvent.
+ mActiveTouchSessions.stream()
+ .map(touchSessionStack -> touchSessionStack.getEventListeners())
+ .flatMap(Collection::stream)
+ .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
}
- // Informing handlers of new sessions is delayed until we have all created so the
- // final session is correct.
- sessionMap.forEach((dreamTouchHandler, touchSession)
- -> dreamTouchHandler.onSessionStart(touchSession));
- }
-
- // Find active sessions and invoke on InputEvent.
- mActiveTouchSessions.stream()
- .map(touchSessionStack -> touchSessionStack.getEventListeners())
- .flatMap(Collection::stream)
- .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
- }
- private Rect getCurrentExclusionRect() {
- return mExclusionRect;
- }
- };
+ private Rect getCurrentExclusionRect() {
+ return mExclusionRect;
+ }
+ };
/**
* The {@link Evaluator} interface allows for callers to inspect a listener from the
@@ -401,71 +403,75 @@
private GestureDetector.OnGestureListener mOnGestureListener =
new GestureDetector.OnGestureListener() {
- private boolean evaluate(Evaluator evaluator) {
- final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
+ private boolean evaluate(Evaluator evaluator) {
+ final Set<TouchSessionImpl> consumingSessions = new HashSet<>();
- // When a gesture is consumed, it is assumed that all touches for the current session
- // should be directed only to those TouchSessions until those sessions are popped. All
- // non-participating sessions are removed from receiving further updates with
- // {@link DreamOverlayTouchMonitor#isolate}.
- final boolean eventConsumed = mActiveTouchSessions.stream()
- .map(touchSession -> {
- boolean consume = touchSession.getGestureListeners()
- .stream()
- .map(listener -> evaluator.evaluate(listener))
- .anyMatch(consumed -> consumed);
+ // When a gesture is consumed, it is assumed that all touches for the current
+ // session
+ // should be directed only to those TouchSessions until those sessions are
+ // popped. All
+ // non-participating sessions are removed from receiving further updates with
+ // {@link DreamOverlayTouchMonitor#isolate}.
+ final boolean eventConsumed = mActiveTouchSessions.stream()
+ .map(touchSession -> {
+ boolean consume = touchSession.getGestureListeners()
+ .stream()
+ .map(listener -> evaluator.evaluate(listener))
+ .anyMatch(consumed -> consumed);
- if (consume) {
- consumingSessions.add(touchSession);
- }
- return consume;
- }).anyMatch(consumed -> consumed);
+ if (consume) {
+ consumingSessions.add(touchSession);
+ }
+ return consume;
+ }).anyMatch(consumed -> consumed);
- if (eventConsumed) {
- DreamOverlayTouchMonitor.this.isolate(consumingSessions);
- }
+ if (eventConsumed) {
+ TouchMonitor.this.isolate(consumingSessions);
+ }
- return eventConsumed;
- }
+ return eventConsumed;
+ }
- // This method is called for gesture events that cannot be consumed.
- private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
- mActiveTouchSessions.stream()
- .map(touchSession -> touchSession.getGestureListeners())
- .flatMap(Collection::stream)
- .forEach(listener -> consumer.accept(listener));
- }
+ // This method is called for gesture events that cannot be consumed.
+ private void observe(Consumer<GestureDetector.OnGestureListener> consumer) {
+ mActiveTouchSessions.stream()
+ .map(touchSession -> touchSession.getGestureListeners())
+ .flatMap(Collection::stream)
+ .forEach(listener -> consumer.accept(listener));
+ }
- @Override
- public boolean onDown(MotionEvent e) {
- return evaluate(listener -> listener.onDown(e));
- }
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return evaluate(listener -> listener.onDown(e));
+ }
- @Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
- return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
- }
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return evaluate(listener -> listener.onFling(e1, e2, velocityX, velocityY));
+ }
- @Override
- public void onLongPress(MotionEvent e) {
- observe(listener -> listener.onLongPress(e));
- }
+ @Override
+ public void onLongPress(MotionEvent e) {
+ observe(listener -> listener.onLongPress(e));
+ }
- @Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
- return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
- }
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return evaluate(listener -> listener.onScroll(e1, e2, distanceX, distanceY));
+ }
- @Override
- public void onShowPress(MotionEvent e) {
- observe(listener -> listener.onShowPress(e));
- }
+ @Override
+ public void onShowPress(MotionEvent e) {
+ observe(listener -> listener.onShowPress(e));
+ }
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return evaluate(listener -> listener.onSingleTapUp(e));
- }
- };
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return evaluate(listener -> listener.onSingleTapUp(e));
+ }
+ };
private InputSessionComponent.Factory mInputSessionFactory;
private InputSession mCurrentInputSession;
@@ -474,25 +480,27 @@
/**
- * Designated constructor for {@link DreamOverlayTouchMonitor}
- * @param executor This executor will be used for maintaining the active listener list to avoid
- * concurrent modification.
- * @param lifecycle {@link DreamOverlayTouchMonitor} will listen to this lifecycle to determine
- * whether touch monitoring should be active.
+ * Designated constructor for {@link TouchMonitor}
+ *
+ * @param executor This executor will be used for maintaining the active listener
+ * list to avoid
+ * concurrent modification.
+ * @param lifecycle {@link TouchMonitor} will listen to this lifecycle to determine
+ * whether touch monitoring should be active.
* @param inputSessionFactory This factory will generate the {@link InputSession} requested by
* the monitor. Each session should be unique and valid when
* returned.
- * @param handlers This set represents the {@link DreamTouchHandler} instances that will
- * participate in touch handling.
+ * @param handlers This set represents the {@link TouchHandler} instances that will
+ * participate in touch handling.
*/
@Inject
- public DreamOverlayTouchMonitor(
+ public TouchMonitor(
@Main Executor executor,
@Background Executor backgroundExecutor,
Lifecycle lifecycle,
InputSessionComponent.Factory inputSessionFactory,
DisplayHelper displayHelper,
- Set<DreamTouchHandler> handlers,
+ Set<TouchHandler> handlers,
IWindowManager windowManagerService,
@DisplayId int displayId) {
mDisplayId = displayId;
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
new file mode 100644
index 0000000..390e53b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchComponent.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.ambient.touch.dagger
+
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule.Companion.TOUCH_HANDLERS
+import com.android.systemui.ambient.touch.TouchHandler
+import com.android.systemui.ambient.touch.TouchMonitor
+import dagger.BindsInstance
+import dagger.Subcomponent
+import javax.inject.Named
+
+/**
+ * {@link AmbientTouchComponent} can be used for setting up a touch environment over the entire
+ * display surface. This allows for implementing behaviors such as swiping up to bring up the
+ * bouncer.
+ */
+@Subcomponent(modules = [AmbientTouchModule::class, ShadeModule::class, BouncerSwipeModule::class])
+interface AmbientTouchComponent {
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(
+ @BindsInstance lifecycleOwner: LifecycleOwner,
+ @BindsInstance
+ @Named(TOUCH_HANDLERS)
+ touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+ ): AmbientTouchComponent
+ }
+
+ /** Builds a [TouchMonitor] */
+ fun getTouchMonitor(): TouchMonitor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
new file mode 100644
index 0000000..a4924d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/AmbientTouchModule.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.ambient.touch.dagger
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.android.systemui.ambient.dagger.AmbientModule
+import com.android.systemui.ambient.touch.TouchHandler
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+import javax.inject.Named
+
+@Module
+interface AmbientTouchModule {
+ companion object {
+ @JvmStatic
+ @Provides
+ fun providesLifecycle(lifecycleOwner: LifecycleOwner): Lifecycle {
+ return lifecycleOwner.lifecycle
+ }
+
+ @Provides
+ @ElementsIntoSet
+ fun providesDreamTouchHandlers(
+ @Named(AmbientModule.TOUCH_HANDLERS)
+ touchHandlers: Set<@JvmSuppressWildcards TouchHandler>
+ ): Set<@JvmSuppressWildcards TouchHandler> {
+ return touchHandlers
+ }
+
+ const val INPUT_SESSION_NAME = "INPUT_SESSION_NAME"
+ const val PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
index a5db2ff..dac2d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/BouncerSwipeModule.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.util.TypedValue;
import android.view.VelocityTracker;
+import com.android.systemui.ambient.touch.BouncerSwipeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -66,7 +66,7 @@
*/
@Provides
@IntoSet
- public static DreamTouchHandler providesBouncerSwipeTouchHandler(
+ public static TouchHandler providesBouncerSwipeTouchHandler(
BouncerSwipeTouchHandler touchHandler) {
return touchHandler;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
index 0b14521..203fb64 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionComponent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.PILFER_ON_GESTURE_CONSUME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.PILFER_ON_GESTURE_CONSUME;
import android.view.GestureDetector;
-import com.android.systemui.dreams.touch.InputSession;
+import com.android.systemui.ambient.touch.InputSession;
import com.android.systemui.shared.system.InputChannelCompat;
import dagger.BindsInstance;
@@ -42,6 +42,7 @@
*/
@Subcomponent.Factory
interface Factory {
+ /** */
InputSessionComponent create(@Named(INPUT_SESSION_NAME) @BindsInstance String name,
@BindsInstance InputChannelCompat.InputEventListener inputEventListener,
@BindsInstance GestureDetector.OnGestureListener gestureListener,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
index dfab666..99dbdee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/InputSessionModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/InputSessionModule.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
-import static com.android.systemui.dreams.touch.dagger.DreamTouchModule.INPUT_SESSION_NAME;
+import static com.android.systemui.ambient.touch.dagger.AmbientTouchModule.INPUT_SESSION_NAME;
import android.view.GestureDetector;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
index 0f08d37..bc2f354 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/ShadeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/dagger/ShadeModule.java
@@ -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.
@@ -14,14 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.dagger;
+package com.android.systemui.ambient.touch.dagger;
import android.content.res.Resources;
+import com.android.systemui.ambient.touch.ShadeTouchHandler;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dreams.touch.CommunalTouchHandler;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.ShadeTouchHandler;
import com.android.systemui.res.R;
import dagger.Binds;
@@ -43,23 +42,14 @@
public static final String NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT =
"notification_shade_gesture_initiation_height";
- /** Width of swipe gesture edge to show communal hub. */
- public static final String COMMUNAL_GESTURE_INITIATION_WIDTH =
- "communal_gesture_initiation_width";
-
/**
* Provides {@link ShadeTouchHandler} to handle notification swipe down over dream.
*/
@Binds
@IntoSet
- public abstract DreamTouchHandler providesNotificationShadeTouchHandler(
+ public abstract TouchHandler providesNotificationShadeTouchHandler(
ShadeTouchHandler touchHandler);
- /** Provides {@link CommunalTouchHandler}. */
- @Binds
- @IntoSet
- public abstract DreamTouchHandler bindCommunalTouchHandler(CommunalTouchHandler touchHandler);
-
/**
* Provides the height of the gesture area for notification swipe down.
*/
@@ -69,12 +59,4 @@
return resources.getDimensionPixelSize(R.dimen.dream_overlay_status_bar_height);
}
- /**
- * Provides the width of the gesture area for swiping open communal hub.
- */
- @Provides
- @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
- public static int providesCommunalGestureInitiationWidth(@Main Resources resources) {
- return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 776b7bd..94c9982 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
index 01e4d04..c453ddb 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/BouncerlessScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerlessScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import android.os.PowerManager;
import android.os.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
index 61629ef..0054352 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimController.java
@@ -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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
index 0d0dff6..676221d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/ScrimManager.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/ScrimManager.java
@@ -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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim;
+package com.android.systemui.ambient.touch.scrim;
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
-import static com.android.systemui.dreams.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCERLESS_SCRIM_CONTROLLER;
+import static com.android.systemui.ambient.touch.scrim.dagger.ScrimModule.BOUNCER_SCRIM_CONTROLLER;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
rename to packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
index 4ad5161..b07029b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/scrim/dagger/ScrimModule.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/dagger/ScrimModule.java
@@ -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.
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch.scrim.dagger;
+package com.android.systemui.ambient.touch.scrim.dagger;
-import com.android.systemui.dreams.touch.scrim.BouncerScrimController;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.dreams.touch.scrim.ScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerScrimController;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.ambient.touch.scrim.ScrimController;
import dagger.Module;
import dagger.Provides;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 454ed27..a9f985f 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -36,7 +36,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.onSubscriberAdded
@@ -186,7 +186,6 @@
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- flags: SceneContainerFlags,
private val clock: SystemClock,
private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
private val userRepository: UserRepository,
@@ -255,7 +254,7 @@
override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
init {
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
// Hydrate failedAuthenticationAttempts initially and whenever the selected user
// changes.
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 61d1c71..4a60d19 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -323,7 +323,7 @@
overlayParams = updatedOverlayParams
sensorBounds = updatedOverlayParams.sensorBounds
getTouchOverlay()?.let {
- if (addViewRunnable != null) {
+ if (addViewRunnable == null) {
// Only updateViewLayout if there's no pending view to add to WM.
// If there is a pending view, that means the view hasn't been added yet so there's
// no need to update any layouts. Instead the correct params will be used when the
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
index 66b7d7a..d9d3715 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -26,6 +26,7 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieOnCompositionLoadedListener
import com.android.settingslib.widget.LottieColorUtils
import com.android.systemui.Flags.constraintBp
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
@@ -77,6 +78,8 @@
}
launch {
+ var lottieOnCompositionLoadedListener: LottieOnCompositionLoadedListener? = null
+
combine(viewModel.activeAuthType, viewModel.iconSize, ::Pair).collect {
(activeAuthType, iconSize) ->
// Every time after bp shows, [isIconViewLoaded] is set to false in
@@ -94,10 +97,18 @@
* TODO(b/288175072): May be able to remove this once constraint
* layout is implemented
*/
- iconView.removeAllLottieOnCompositionLoadedListener()
- iconView.addLottieOnCompositionLoadedListener {
- promptViewModel.setIsIconViewLoaded(true)
+ if (lottieOnCompositionLoadedListener != null) {
+ iconView.removeLottieOnCompositionLoadedListener(
+ lottieOnCompositionLoadedListener!!
+ )
}
+ lottieOnCompositionLoadedListener =
+ LottieOnCompositionLoadedListener {
+ promptViewModel.setIsIconViewLoaded(true)
+ }
+ iconView.addLottieOnCompositionLoadedListener(
+ lottieOnCompositionLoadedListener!!
+ )
}
AuthType.Face -> {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 66aeda6..207f7db 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -217,10 +217,13 @@
mSwitchBroadcast.setText(mContext.getString(
R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
- changeOutput.setOnClickListener((view) -> {
- mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
- dialog.dismiss();
- });
+ changeOutput.setOnClickListener(
+ (view) -> {
+ // TODO: b/321969740 - Take the userHandle as a parameter and pass it through.
+ // The package name is not sufficient to unambiguously identify an app.
+ mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null);
+ dialog.dismiss();
+ });
cancelBtn.setOnClickListener((view) -> {
if (DEBUG) {
Log.d(TAG, "BroadcastDialog dismiss.");
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
new file mode 100644
index 0000000..e44f054
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import androidx.annotation.StringRes
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+internal sealed class AudioSharingButtonState {
+ object Gone : AudioSharingButtonState()
+ data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
+}
+
+/** Holds business logic for the audio sharing state. */
+@SysUISingleton
+internal class AudioSharingInteractor
+@Inject
+constructor(
+ private val localBluetoothManager: LocalBluetoothManager?,
+ bluetoothStateInteractor: BluetoothStateInteractor,
+ deviceItemInteractor: DeviceItemInteractor,
+ @Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ /** Flow representing the update of AudioSharingButtonState. */
+ internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
+ combine(
+ bluetoothStateInteractor.bluetoothStateUpdate,
+ deviceItemInteractor.deviceItemUpdate
+ ) { bluetoothState, deviceItem ->
+ getButtonState(bluetoothState, deviceItem)
+ }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
+ initialValue = AudioSharingButtonState.Gone
+ )
+
+ private fun getButtonState(
+ bluetoothState: Boolean,
+ deviceItem: List<DeviceItem>
+ ): AudioSharingButtonState {
+ return when {
+ // Don't show button when bluetooth is off
+ !bluetoothState -> AudioSharingButtonState.Gone
+ // Show sharing audio when broadcasting
+ BluetoothUtils.isBroadcasting(localBluetoothManager) ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ // When not broadcasting, don't show button if there's connected source in any device
+ deviceItem.any {
+ BluetoothUtils.hasConnectedBroadcastSource(
+ it.cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ } -> AudioSharingButtonState.Gone
+ // Show audio sharing when there's a connected LE audio device
+ deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ else -> AudioSharingButtonState.Gone
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 94d7af7..17f9e63 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -25,12 +25,17 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
@@ -40,9 +45,10 @@
private val localBluetoothManager: LocalBluetoothManager?,
private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
- internal val bluetoothStateUpdate: StateFlow<Boolean?> =
+ internal val bluetoothStateUpdate: StateFlow<Boolean> =
conflatedCallbackFlow {
val listener =
object : BluetoothCallback {
@@ -64,16 +70,22 @@
localBluetoothManager?.eventManager?.registerCallback(listener)
awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
}
+ .onStart { emit(isBluetoothEnabled()) }
+ .flowOn(backgroundDispatcher)
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
- initialValue = null
+ initialValue = false
)
- internal var isBluetoothEnabled: Boolean
- get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
- set(value) {
- if (isBluetoothEnabled != value) {
+ suspend fun isBluetoothEnabled(): Boolean =
+ withContext(backgroundDispatcher) {
+ localBluetoothManager?.bluetoothAdapter?.isEnabled == true
+ }
+
+ suspend fun setBluetoothEnabled(value: Boolean) {
+ withContext(backgroundDispatcher) {
+ if (isBluetoothEnabled() != value) {
localBluetoothManager?.bluetoothAdapter?.apply {
if (value) enable() else disable()
logger.logBluetoothState(
@@ -83,6 +95,7 @@
}
}
}
+ }
companion object {
private const val TAG = "BtStateInteractor"
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index c7d171d..dd8c0df 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -27,6 +27,7 @@
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Switch
@@ -59,7 +60,6 @@
internal constructor(
@Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
@Assisted private val cachedContentHeight: Int,
- @Assisted private val bluetoothToggleInitialValue: Boolean,
@Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
@Assisted private val dismissListener: Runnable,
@Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@
private val systemuiDialogFactory: SystemUIDialog.Factory,
) : SystemUIDialog.Delegate {
- private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
- MutableStateFlow(bluetoothToggleInitialValue)
+ private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
internal val bluetoothStateToggle
get() = mutableBluetoothStateToggle.asStateFlow()
@@ -99,7 +98,6 @@
fun create(
initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
cachedContentHeight: Int,
- bluetoothEnabled: Boolean,
dialogCallback: BluetoothTileDialogCallback,
dimissListener: Runnable
): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@
getPairNewDeviceButton(dialog).setOnClickListener {
bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
}
+ getAudioSharingButtonView(dialog).setOnClickListener {
+ bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+ }
getScrollViewContent(dialog).apply {
minimumHeight =
resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@
getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
}
+ internal fun onAudioSharingButtonUpdated(
+ dialog: SystemUIDialog,
+ visibility: Int,
+ label: String?
+ ) {
+ getAudioSharingButtonView(dialog).apply {
+ this.visibility = visibility
+ label?.let { text = it }
+ }
+ }
+
private fun setupToggle(dialog: SystemUIDialog) {
val toggleView = getToggleView(dialog)
- toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothStateToggle.value = isChecked
view.apply {
@@ -259,6 +270,10 @@
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
}
+ private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
+ return dialog.requireViewById(R.id.audio_sharing_button)
+ }
+
private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
}
@@ -412,6 +427,8 @@
const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
"com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
+ const val ACTION_AUDIO_SHARING =
+ "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index add1647..b592b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -30,9 +30,12 @@
@UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
@UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500),
@UiEvent(doc = "Active device clicked to disconnect") ACTIVE_DEVICE_DISCONNECT(1507),
+ @UiEvent(doc = "Audio sharing device clicked, do nothing") AUDIO_SHARING_DEVICE_CLICKED(1699),
@UiEvent(doc = "Connected other device clicked to disconnect")
CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
- @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617);
+ @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
+ @UiEvent(doc = "The audio sharing button is clicked")
+ BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
override fun getId() = metricId
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index e65b657..eb919e3 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -28,9 +28,11 @@
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
@@ -61,6 +63,7 @@
private val deviceItemInteractor: DeviceItemInteractor,
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
+ private val audioSharingInteractor: AudioSharingInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
private val uiEventLogger: UiEventLogger,
@@ -119,7 +122,8 @@
dialog,
it.take(MAX_DEVICE_ITEM_ENTRY),
showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
- showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ showPairNewDevice =
+ bluetoothStateInteractor.isBluetoothEnabled()
)
animateProgressBar(dialog, false)
}
@@ -142,10 +146,25 @@
}
.launchIn(this)
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ audioSharingInteractor.audioSharingButtonStateUpdate
+ .onEach {
+ if (it is AudioSharingButtonState.Visible) {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ VISIBLE,
+ context.getString(it.resId)
+ )
+ } else {
+ dialogDelegate.onAudioSharingButtonUpdated(dialog, GONE, null)
+ }
+ }
+ .launchIn(this)
+ }
+
// bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
// the device item list.
bluetoothStateInteractor.bluetoothStateUpdate
- .filterNotNull()
.onEach {
dialogDelegate.onBluetoothStateUpdated(
dialog,
@@ -165,9 +184,10 @@
// bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
// send the new value to the bluetoothStateInteractor and animate the progress bar.
dialogDelegate.bluetoothStateToggle
+ .filterNotNull()
.onEach {
dialogDelegate.animateProgressBar(dialog, true)
- bluetoothStateInteractor.isBluetoothEnabled = it
+ bluetoothStateInteractor.setBluetoothEnabled(it)
}
.launchIn(this)
@@ -222,11 +242,10 @@
return bluetoothDialogDelegateFactory.create(
UiProperties.build(
- bluetoothStateInteractor.isBluetoothEnabled,
+ bluetoothStateInteractor.isBluetoothEnabled(),
isAutoOnToggleFeatureAvailable()
),
cachedContentHeight,
- bluetoothStateInteractor.isBluetoothEnabled,
this@BluetoothTileDialogViewModel,
{ cancelJob() }
)
@@ -256,6 +275,11 @@
startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
}
+ override fun onAudioSharingButtonClicked(view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED)
+ startSettingsActivity(Intent(ACTION_AUDIO_SHARING), view)
+ }
+
private fun cancelJob() {
job?.cancel()
job = null
@@ -312,4 +336,5 @@
fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
+ fun onAudioSharingButtonClicked(view: View)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
index dc5efef..0ea98d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItem.kt
@@ -37,6 +37,7 @@
enum class DeviceItemType {
ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
CONNECTED_BLUETOOTH_DEVICE,
SAVED_BLUETOOTH_DEVICE,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index f04ba75..49d0847 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -21,13 +21,16 @@
import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
+import com.android.settingslib.flags.Flags.enableLeAudioSharing
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
private val connected = R.string.quick_settings_bluetooth_device_connected
+private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
private val saved = R.string.quick_settings_bluetooth_device_saved
private val actionAccessibilityLabelActivate =
R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
@@ -39,35 +42,81 @@
abstract fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager,
): Boolean
abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
+
+ companion object {
+ @JvmStatic
+ fun createDeviceItem(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ type: DeviceItemType,
+ connectionSummary: String,
+ background: Int,
+ actionAccessibilityLabel: String
+ ): DeviceItem {
+ return DeviceItem(
+ type = type,
+ cachedBluetoothDevice = cachedDevice,
+ deviceName = cachedDevice.name,
+ connectionSummary = connectionSummary,
+ iconWithDescription =
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
+ Pair(it.first, it.second)
+ },
+ background = background,
+ isEnabled = !cachedDevice.isBusy,
+ actionAccessibilityLabel = actionAccessibilityLabel
+ )
+ }
+ }
}
internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary ?: "",
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = backgroundOn,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary ?: "",
+ backgroundOn,
+ context.getString(actionAccessibilityLabelDisconnect)
+ )
+ }
+}
+
+internal class AudioSharingMediaDeviceItemFactory(
+ private val localBluetoothManager: LocalBluetoothManager?
+) : DeviceItemFactory() {
+ override fun isFilterMatched(
+ context: Context,
+ cachedDevice: CachedBluetoothDevice,
+ audioManager: AudioManager
+ ): Boolean {
+ return enableLeAudioSharing() &&
+ BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+ }
+
+ override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(audioSharing),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
+ ""
)
}
}
@@ -76,7 +125,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -87,27 +136,21 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, audioManager)
}
- // TODO(b/298124674): move create() to the abstract class to reduce duplicate code
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -116,7 +159,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
BluetoothUtils.isAvailableHearingDevice(cachedDevice)
@@ -127,32 +170,25 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
} else {
BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelDisconnect),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(connected),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelDisconnect)
)
}
}
@@ -161,32 +197,26 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
- !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
- context,
- cachedDevice.getDevice()
- ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
+ !cachedDevice.isConnected
} else {
cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
}
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
- return DeviceItem(
- type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedDevice,
- deviceName = cachedDevice.name,
- connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(saved),
- iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
- isEnabled = !cachedDevice.isBusy,
- actionAccessibilityLabel = context.getString(actionAccessibilityLabelActivate),
+ return createDeviceItem(
+ context,
+ cachedDevice,
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+ cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
+ ?: context.getString(saved),
+ if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
+ context.getString(actionAccessibilityLabelActivate)
)
}
}
@@ -195,7 +225,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
): Boolean {
return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
!BluetoothUtils.isExclusivelyManagedBluetoothDevice(
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 4e28caf..66e593b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -113,6 +113,7 @@
private var deviceItemFactoryList: List<DeviceItemFactory> =
listOf(
ActiveMediaDeviceItemFactory(),
+ AudioSharingMediaDeviceItemFactory(localBluetoothManager),
AvailableMediaDeviceItemFactory(),
ConnectedDeviceItemFactory(),
SavedDeviceItemFactory()
@@ -121,6 +122,7 @@
private var displayPriority: List<DeviceItemType> =
listOf(
DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
DeviceItemType.SAVED_BLUETOOTH_DEVICE,
@@ -177,6 +179,9 @@
disconnect()
uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT)
}
+ DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
+ }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 02a40d9..dd71bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -16,16 +16,23 @@
package com.android.systemui.bouncer.domain.interactor
+import android.app.StatusBarManager.SESSION_KEYGUARD
import com.android.compose.animation.scene.SceneKey
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
import com.android.systemui.bouncer.data.repository.BouncerRepository
+import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.log.SessionTracker
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -50,6 +57,8 @@
private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ private val uiEventLogger: UiEventLogger,
+ private val sessionTracker: SessionTracker,
sceneInteractor: SceneInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
@@ -162,6 +171,18 @@
) {
_onIncorrectBouncerInput.emit(Unit)
}
+
+ if (authenticationInteractor.getAuthenticationMethod() in setOf(Pin, Password, Pattern)) {
+ if (authResult == AuthenticationResult.SUCCEEDED) {
+ uiEventLogger.log(BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS)
+ } else if (authResult == AuthenticationResult.FAILED) {
+ uiEventLogger.log(
+ BouncerUiEvent.BOUNCER_PASSWORD_FAILURE,
+ sessionTracker.getSessionId(SESSION_KEYGUARD)
+ )
+ }
+ }
+
return authResult
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
index e789475..62ef365 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlags.kt
@@ -18,7 +18,7 @@
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import dagger.Module
import dagger.Provides
@@ -42,11 +42,10 @@
fun isOnlyComposeBouncerEnabled(): Boolean
}
-class ComposeBouncerFlagsImpl(private val sceneContainerFlags: SceneContainerFlags) :
- ComposeBouncerFlags {
+class ComposeBouncerFlagsImpl() : ComposeBouncerFlags {
override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return sceneContainerFlags.isEnabled() || Flags.composeBouncer()
+ return SceneContainerFlag.isEnabled || Flags.composeBouncer()
}
@Deprecated(
@@ -55,7 +54,7 @@
replaceWith = ReplaceWith("isComposeBouncerOrSceneContainerEnabled()")
)
override fun isOnlyComposeBouncerEnabled(): Boolean {
- return !sceneContainerFlags.isEnabled() && Flags.composeBouncer()
+ return !SceneContainerFlag.isEnabled && Flags.composeBouncer()
}
}
@@ -63,7 +62,7 @@
object ComposeBouncerFlagsModule {
@Provides
@SysUISingleton
- fun impl(sceneContainerFlags: SceneContainerFlags): ComposeBouncerFlags {
- return ComposeBouncerFlagsImpl(sceneContainerFlags)
+ fun impl(): ComposeBouncerFlags {
+ return ComposeBouncerFlagsImpl()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
new file mode 100644
index 0000000..3be5499
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/shared/logging/BouncerUiEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.shared.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/**
+ * Legacy bouncer UI events {@link com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent}.
+ * Only contains that used by metrics.
+ */
+enum class BouncerUiEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Bouncer is dismissed using extended security access.")
+ BOUNCER_DISMISS_EXTENDED_ACCESS(413),
+
+ // PASSWORD here includes password, pattern, and pin.
+ @UiEvent(doc = "Bouncer is successfully unlocked using password.")
+ BOUNCER_PASSWORD_SUCCESS(418),
+ @UiEvent(doc = "An attempt to unlock bouncer using password has failed.")
+ BOUNCER_PASSWORD_FAILURE(419);
+
+ override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 12cac92..4c2380c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -135,8 +135,11 @@
onIntentionalUserInput()
- mutablePinInput.value = pinInput.append(input)
- tryAuthenticate(useAutoConfirm = true)
+ val maxInputLength = hintedPinLength.value ?: Int.MAX_VALUE
+ if (pinInput.getPin().size < maxInputLength) {
+ mutablePinInput.value = pinInput.append(input)
+ tryAuthenticate(useAutoConfirm = true)
+ }
}
/** Notifies that the user clicked the backspace button. */
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
index af467ef..613280c 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingModule.java
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.phone.NotificationTapHelper;
import dagger.Binds;
@@ -51,9 +51,8 @@
@SysUISingleton
static FalsingCollector providesFalsingCollectorLegacy(
FalsingCollectorImpl impl,
- FalsingCollectorNoOp noOp,
- SceneContainerFlags flags) {
- return flags.isEnabled() ? noOp : impl;
+ FalsingCollectorNoOp noOp) {
+ return SceneContainerFlag.isEnabled() ? noOp : impl;
}
/** Provides the actual {@link FalsingCollector}. */
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
index d4a1f74..0c181e9 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
@@ -16,14 +16,14 @@
package com.android.systemui.common.coroutine
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow as wrapped
import kotlin.experimental.ExperimentalTypeInference
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.callbackFlow
+@Deprecated("Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead")
object ConflatedCallbackFlow {
/**
@@ -32,9 +32,15 @@
* consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
* we drop the oldest values automatically instead of suspending the producer.
*/
- @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
- @OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
+ @Deprecated(
+ "Use com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow instead",
+ ReplaceWith(
+ "conflatedCallbackFlow",
+ "com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow"
+ )
+ )
+ @OptIn(ExperimentalTypeInference::class)
fun <T> conflatedCallbackFlow(
@BuilderInference block: suspend ProducerScope<T>.() -> Unit,
- ): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
+ ): Flow<T> = wrapped(block)
}
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 373e1c9..619e052 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
@@ -55,7 +55,7 @@
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
@@ -107,7 +107,6 @@
private val userManager: UserManager,
private val dockManager: DockManager,
sceneInteractor: SceneInteractor,
- sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
@@ -216,7 +215,7 @@
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- flow { emit(sceneContainerFlags.isEnabled()) }
+ flow { emit(SceneContainerFlag.isEnabled) }
.flatMapLatest { sceneContainerEnabled ->
if (sceneContainerEnabled) {
sceneInteractor.currentScene.map { it == Scenes.Communal }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7d86e06..6b85d30 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
+import com.android.systemui.ambient.dagger.AmbientModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.authentication.AuthenticationModule;
@@ -162,14 +163,14 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import kotlinx.coroutines.CoroutineScope;
+
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Named;
-import kotlinx.coroutines.CoroutineScope;
-
/**
* A dagger module for injecting components of System UI that are required by System UI.
*
@@ -183,6 +184,7 @@
@Module(includes = {
AccessibilityModule.class,
AccessibilityRepositoryModule.class,
+ AmbientModule.class,
AppOpsModule.class,
AssistModule.class,
AuthenticationModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
deleted file mode 100644
index 989b0de..0000000
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.display.ui.view
-
-import android.content.Context
-import android.os.Bundle
-import android.view.View
-import android.view.WindowInsets
-import android.widget.TextView
-import androidx.core.view.updatePadding
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
-import kotlin.math.max
-
-/**
- * Dialog used to decide what to do with a connected display.
- *
- * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
- * pressed.
- */
-class MirroringConfirmationDialog(
- context: Context,
- private val onStartMirroringClickListener: View.OnClickListener,
- private val onCancelMirroring: View.OnClickListener,
- private val navbarBottomInsetsProvider: () -> Int,
- configurationController: ConfigurationController? = null,
- private val showConcurrentDisplayInfo: Boolean = false,
- theme: Int = R.style.Theme_SystemUI_Dialog,
-) : SystemUIBottomSheetDialog(context, configurationController, theme) {
-
- private lateinit var mirrorButton: TextView
- private lateinit var dismissButton: TextView
- private lateinit var dualDisplayWarning: TextView
- private lateinit var bottomSheet: View
- private var enabledPressed = false
- private val defaultDialogBottomInset =
- context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.connected_display_dialog)
-
- mirrorButton =
- requireViewById<TextView>(R.id.enable_display).apply {
- setOnClickListener(onStartMirroringClickListener)
- enabledPressed = true
- }
- dismissButton =
- requireViewById<TextView>(R.id.cancel).apply { setOnClickListener(onCancelMirroring) }
-
- dualDisplayWarning =
- requireViewById<TextView>(R.id.dual_display_warning).apply {
- visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
- }
-
- bottomSheet = requireViewById(R.id.cd_bottom_sheet)
-
- setOnDismissListener {
- if (!enabledPressed) {
- onCancelMirroring.onClick(null)
- }
- }
- setupInsets()
- }
-
- private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
- // This avoids overlap between dialog content and navigation bars.
- // we only care about the bottom inset as in all other configuration where navigations
- // are in other display sides there is no overlap with the dialog.
- bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
- }
-
- override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
- val navbarType = WindowInsets.Type.navigationBars()
- if (changedTypes and navbarType != 0) {
- setupInsets(insets.getInsets(navbarType).bottom)
- }
- }
-
- override fun onConfigurationChanged() {
- super.onConfigurationChanged()
- setupInsets()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
new file mode 100644
index 0000000..19b2673
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegate.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.display.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import android.widget.TextView
+import androidx.annotation.StyleRes
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.updatePadding
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.DialogDelegate
+import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import javax.inject.Inject
+import kotlin.math.max
+
+/**
+ * Dialog used to decide what to do with a connected display.
+ *
+ * [onCancelMirroring] is called **only** if mirroring didn't start, or when the dismiss button is
+ * pressed.
+ */
+class MirroringConfirmationDialogDelegate
+@VisibleForTesting
+constructor(
+ context: Context,
+ private val showConcurrentDisplayInfo: Boolean = false,
+ private val onStartMirroringClickListener: View.OnClickListener,
+ private val onCancelMirroring: View.OnClickListener,
+ private val navbarBottomInsetsProvider: () -> Int,
+) : DialogDelegate<Dialog> {
+
+ private lateinit var mirrorButton: TextView
+ private lateinit var dismissButton: TextView
+ private lateinit var dualDisplayWarning: TextView
+ private lateinit var bottomSheet: View
+ private var enabledPressed = false
+ private val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+
+ override fun onCreate(dialog: Dialog, savedInstanceState: Bundle?) {
+ dialog.setContentView(R.layout.connected_display_dialog)
+
+ mirrorButton =
+ dialog.requireViewById<TextView>(R.id.enable_display).apply {
+ setOnClickListener(onStartMirroringClickListener)
+ enabledPressed = true
+ }
+ dismissButton =
+ dialog.requireViewById<TextView>(R.id.cancel).apply {
+ setOnClickListener(onCancelMirroring)
+ }
+
+ dualDisplayWarning =
+ dialog.requireViewById<TextView>(R.id.dual_display_warning).apply {
+ visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
+ }
+
+ bottomSheet = dialog.requireViewById(R.id.cd_bottom_sheet)
+
+ dialog.setOnDismissListener {
+ if (!enabledPressed) {
+ onCancelMirroring.onClick(null)
+ }
+ }
+ setupInsets()
+ }
+
+ override fun onStart(dialog: Dialog) {
+ dialog.window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+ }
+
+ override fun onStop(dialog: Dialog) {
+ dialog.window?.decorView?.setWindowInsetsAnimationCallback(null)
+ }
+
+ private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
+ // This avoids overlap between dialog content and navigation bars.
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ }
+
+ override fun onConfigurationChanged(dialog: Dialog, configuration: Configuration) {
+ setupInsets()
+ }
+
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private var lastInsets: WindowInsets? = null
+
+ override fun onEnd(animation: WindowInsetsAnimation) {
+ lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+ }
+
+ override fun onProgress(
+ insets: WindowInsets,
+ animations: MutableList<WindowInsetsAnimation>,
+ ): WindowInsets {
+ lastInsets = insets
+ onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+ return insets
+ }
+
+ private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+ animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+
+ private fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+ val navbarType = WindowInsets.Type.navigationBars()
+ if (changedTypes and navbarType != 0) {
+ setupInsets(insets.getInsets(navbarType).bottom)
+ }
+ }
+ }
+
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val dialogFactory: SystemUIBottomSheetDialog.Factory,
+ ) {
+
+ fun createDialog(
+ showConcurrentDisplayInfo: Boolean = false,
+ onStartMirroringClickListener: View.OnClickListener,
+ onCancelMirroring: View.OnClickListener,
+ navbarBottomInsetsProvider: () -> Int,
+ @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+ ): Dialog =
+ dialogFactory.create(
+ delegate =
+ MirroringConfirmationDialogDelegate(
+ context = context,
+ showConcurrentDisplayInfo = showConcurrentDisplayInfo,
+ onStartMirroringClickListener = onStartMirroringClickListener,
+ onCancelMirroring = onCancelMirroring,
+ navbarBottomInsetsProvider = navbarBottomInsetsProvider,
+ ),
+ theme = theme,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index fbf0538..81ea2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -25,8 +25,7 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
-import com.android.systemui.display.ui.view.MirroringConfirmationDialog
-import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.display.ui.view.MirroringConfirmationDialogDelegate
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -54,7 +53,7 @@
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val configurationController: ConfigurationController,
+ private val bottomSheetFactory: MirroringConfirmationDialogDelegate.Factory,
) : CoreStartable {
private var dialog: Dialog? = null
@@ -91,8 +90,8 @@
private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) {
hideDialog()
dialog =
- MirroringConfirmationDialog(
- context,
+ bottomSheetFactory
+ .createDialog(
onStartMirroringClickListener = {
scope.launch(bgDispatcher) { pendingDisplay.enable() }
hideDialog()
@@ -102,7 +101,6 @@
hideDialog()
},
navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom },
- configurationController,
showConcurrentDisplayInfo = concurrentDisplaysInProgess
)
.apply { show() }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index d0f2559..5a036b1 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -33,14 +33,14 @@
import com.android.app.animation.Interpolators;
import com.android.dream.lowlight.LowLightTransitionCoordinator;
-import com.android.systemui.res.R;
+import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
-import com.android.systemui.dreams.touch.scrim.BouncerlessScrimController;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 675e8de..1135afe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -43,11 +43,12 @@
import com.android.internal.policy.PhoneWindow;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.ambient.touch.TouchMonitor;
+import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
import com.android.systemui.complication.Complication;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -94,6 +95,8 @@
private final ComplicationComponent mComplicationComponent;
+ private final AmbientTouchComponent mAmbientTouchComponent;
+
private final com.android.systemui.dreams.complication.dagger.ComplicationComponent
mDreamComplicationComponent;
@@ -102,7 +105,7 @@
private final DreamOverlayLifecycleOwner mLifecycleOwner;
private final LifecycleRegistry mLifecycleRegistry;
- private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor;
+ private TouchMonitor mTouchMonitor;
private final KeyguardUpdateMonitorCallback mKeyguardCallback =
new KeyguardUpdateMonitorCallback() {
@@ -162,6 +165,7 @@
com.android.systemui.dreams.complication.dagger.ComplicationComponent.Factory
dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
+ AmbientTouchComponent.Factory ambientTouchComponentFactory,
DreamOverlayStateController stateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
UiEventLogger uiEventLogger,
@@ -194,9 +198,11 @@
mDreamComplicationComponent = dreamComplicationComponentFactory.create(
mComplicationComponent.getVisibilityController(), touchInsetManager);
mDreamOverlayComponent = dreamOverlayComponentFactory.create(lifecycleOwner,
- mComplicationComponent.getComplicationHostViewController(), touchInsetManager,
+ mComplicationComponent.getComplicationHostViewController(), touchInsetManager);
+ mAmbientTouchComponent = ambientTouchComponentFactory.create(lifecycleOwner,
new HashSet<>(Arrays.asList(
- mDreamComplicationComponent.getHideComplicationTouchHandler())));
+ mDreamComplicationComponent.getHideComplicationTouchHandler(),
+ mDreamOverlayComponent.getCommunalTouchHandler())));
mLifecycleOwner = lifecycleOwner;
mLifecycleRegistry = mLifecycleOwner.getRegistry();
@@ -239,8 +245,8 @@
mDreamOverlayContainerViewController =
mDreamOverlayComponent.getDreamOverlayContainerViewController();
- mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor();
- mDreamOverlayTouchMonitor.init();
+ mTouchMonitor = mAmbientTouchComponent.getTouchMonitor();
+ mTouchMonitor.init();
mStateController.setShouldShowComplications(shouldShowComplications());
@@ -370,7 +376,7 @@
mStateController.setEntryAnimationsFinished(false);
mDreamOverlayContainerViewController = null;
- mDreamOverlayTouchMonitor = null;
+ mTouchMonitor = null;
mWindow = null;
mStarted = false;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
index d525ce3..f8ae5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/HideComplicationTouchHandler.java
@@ -26,11 +26,11 @@
import androidx.annotation.Nullable;
+import com.android.systemui.ambient.touch.TouchHandler;
+import com.android.systemui.ambient.touch.TouchMonitor;
import com.android.systemui.complication.Complication;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -47,12 +47,12 @@
* {@link HideComplicationTouchHandler} is responsible for hiding the overlay complications from
* visibility whenever there is touch interactions outside the overlay. The overlay interaction
* scope includes touches to the complication plus any touch entry region for gestures as specified
- * to the {@link DreamOverlayTouchMonitor}.
+ * to the {@link TouchMonitor}.
*
- * This {@link DreamTouchHandler} is also responsible for fading in the complications at the end
- * of the {@link com.android.systemui.dreams.touch.DreamTouchHandler.TouchSession}.
+ * This {@link TouchHandler} is also responsible for fading in the complications at the end
+ * of the {@link TouchHandler.TouchSession}.
*/
-public class HideComplicationTouchHandler implements DreamTouchHandler {
+public class HideComplicationTouchHandler implements TouchHandler {
private static final String TAG = "HideComplicationHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index ba74742..31710ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -25,6 +25,7 @@
import com.android.dream.lowlight.dagger.LowLightDreamModule;
import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.ambient.touch.scrim.dagger.ScrimModule;
import com.android.systemui.complication.dagger.RegisteredComplicationsModule;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -34,7 +35,6 @@
import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
-import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule;
import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
index cb587c2..16276fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -16,19 +16,14 @@
package com.android.systemui.dreams.dagger;
-import static com.android.systemui.dreams.dagger.DreamOverlayModule.DREAM_TOUCH_HANDLERS;
-
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import android.annotation.Nullable;
-
import androidx.lifecycle.LifecycleOwner;
import com.android.systemui.complication.ComplicationHostViewController;
import com.android.systemui.dreams.DreamOverlayContainerViewController;
-import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.dreams.touch.dagger.DreamTouchModule;
+import com.android.systemui.dreams.touch.CommunalTouchHandler;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.touch.TouchInsetManager;
import dagger.BindsInstance;
@@ -36,17 +31,15 @@
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
-import java.util.Set;
-import javax.inject.Named;
import javax.inject.Scope;
/**
* Dagger subcomponent for {@link DreamOverlayModule}.
*/
@Subcomponent(modules = {
- DreamTouchModule.class,
DreamOverlayModule.class,
+ CommunalTouchModule.class
})
@DreamOverlayComponent.DreamOverlayScope
public interface DreamOverlayComponent {
@@ -56,9 +49,7 @@
DreamOverlayComponent create(
@BindsInstance LifecycleOwner lifecycleOwner,
@BindsInstance ComplicationHostViewController complicationHostViewController,
- @BindsInstance TouchInsetManager touchInsetManager,
- @BindsInstance @Named(DREAM_TOUCH_HANDLERS) @Nullable
- Set<DreamTouchHandler> dreamTouchHandlers);
+ @BindsInstance TouchInsetManager touchInsetManager);
}
/** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
@@ -70,6 +61,6 @@
/** Builds a {@link DreamOverlayContainerViewController}. */
DreamOverlayContainerViewController getDreamOverlayContainerViewController();
- /** Builds a {@link DreamOverlayTouchMonitor} */
- DreamOverlayTouchMonitor getDreamOverlayTouchMonitor();
+ /** Builds communal touch handler */
+ CommunalTouchHandler getCommunalTouchHandler();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 34dd1008..999e681 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.dagger;
-import android.annotation.Nullable;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -25,26 +24,20 @@
import androidx.lifecycle.LifecycleOwner;
import com.android.internal.util.Preconditions;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.DreamOverlayContainerView;
import com.android.systemui.dreams.DreamOverlayStatusBarView;
-import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
import com.android.systemui.touch.TouchInsetManager;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
import javax.inject.Named;
/** Dagger module for {@link DreamOverlayComponent}. */
@Module
public abstract class DreamOverlayModule {
- public static final String DREAM_TOUCH_HANDLERS = "dream_touch_handlers";
public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view";
public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset";
public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
@@ -169,11 +162,4 @@
static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
return lifecycleOwner.getLifecycle();
}
-
- @Provides
- @ElementsIntoSet
- static Set<DreamTouchHandler> providesDreamTouchHandlers(
- @Named(DREAM_TOUCH_HANDLERS) @Nullable Set<DreamTouchHandler> touchHandlers) {
- return touchHandlers != null ? touchHandlers : new HashSet<>();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index 13588c2..1c047dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -16,7 +16,6 @@
package com.android.systemui.dreams.touch;
-import static com.android.systemui.dreams.touch.dagger.ShadeModule.COMMUNAL_GESTURE_INITIATION_WIDTH;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.graphics.Rect;
@@ -27,7 +26,9 @@
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
+import com.android.systemui.ambient.touch.TouchHandler;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.dreams.touch.dagger.CommunalTouchModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.util.Optional;
@@ -36,8 +37,8 @@
import javax.inject.Inject;
import javax.inject.Named;
-/** {@link DreamTouchHandler} responsible for handling touches to open communal hub. **/
-public class CommunalTouchHandler implements DreamTouchHandler {
+/** {@link TouchHandler} responsible for handling touches to open communal hub. **/
+public class CommunalTouchHandler implements TouchHandler {
private final int mInitiationWidth;
private final Optional<CentralSurfaces> mCentralSurfaces;
private final Lifecycle mLifecycle;
@@ -53,7 +54,7 @@
@Inject
public CommunalTouchHandler(
Optional<CentralSurfaces> centralSurfaces,
- @Named(COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
+ @Named(CommunalTouchModule.COMMUNAL_GESTURE_INITIATION_WIDTH) int initiationWidth,
CommunalInteractor communalInteractor,
Lifecycle lifecycle) {
mInitiationWidth = initiationWidth;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
new file mode 100644
index 0000000..927ea4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/CommunalTouchModule.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.dreams.touch.dagger
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+interface CommunalTouchModule {
+ companion object {
+ /** Provides the width of the gesture area for swiping open communal hub. */
+ @JvmStatic
+ @Provides
+ @Named(COMMUNAL_GESTURE_INITIATION_WIDTH)
+ fun providesCommunalGestureInitiationWidth(@Main resources: Resources): Int {
+ return resources.getDimensionPixelSize(R.dimen.communal_gesture_initiation_width)
+ }
+
+ /** Width of swipe gesture edge to show communal hub. */
+ const val COMMUNAL_GESTURE_INITIATION_WIDTH = "communal_gesture_initiation_width"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
deleted file mode 100644
index b719126..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/DreamTouchModule.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.touch.dagger;
-
-import dagger.Module;
-
-/**
- * {@link DreamTouchModule} encapsulates dream touch-related components.
- */
-@Module(includes = {
- BouncerSwipeModule.class,
- ShadeModule.class,
- }, subcomponents = {
- InputSessionComponent.class,
-})
-public interface DreamTouchModule {
- String INPUT_SESSION_NAME = "INPUT_SESSION_NAME";
- String PILFER_ON_GESTURE_CONSUME = "PILFER_ON_GESTURE_CONSUME";
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2a9dad0..21af0a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1373,7 +1373,7 @@
private final Lazy<DreamViewModel> mDreamViewModel;
private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
- private Boolean mShowCommunalByDefault;
+ private boolean mShowCommunalByDefault = false;
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
@@ -3559,15 +3559,14 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
@Nullable ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer, KeyguardBypassController bypassController) {
+ View notificationContainer) {
mCentralSurfaces = centralSurfaces;
mKeyguardViewControllerLazy.get().registerCentralSurfaces(
centralSurfaces,
shadeLockscreenInteractor,
shadeExpansionStateManager,
biometricUnlockController,
- notificationContainer,
- bypassController);
+ notificationContainer);
return mKeyguardViewControllerLazy.get();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index bf1f074..eef4b97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -27,7 +27,7 @@
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -52,7 +52,6 @@
@Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
- private val sceneContainerFlags: SceneContainerFlags,
private val sceneInteractor: SceneInteractor,
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -75,7 +74,7 @@
get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
private val isBouncerSceneActive: Flow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged()
} else {
flowOf(false)
@@ -115,7 +114,7 @@
.distinctUntilChanged()
private fun isBouncerActive(): Boolean {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return sceneInteractor.currentScene.value == Scenes.Bouncer
}
return primaryBouncerInteractor.isBouncerShowing() &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c476948..7224536 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -44,7 +44,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
@@ -84,7 +84,6 @@
private val repository: KeyguardRepository,
private val commandQueue: CommandQueue,
powerInteractor: PowerInteractor,
- sceneContainerFlags: SceneContainerFlags,
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
shadeRepository: ShadeRepository,
@@ -331,7 +330,7 @@
/** Whether to animate the next doze mode transition. */
val animateDozingTransitions: Flow<Boolean> by lazy {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneInteractorProvider
.get()
.transitioningTo
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 80e94a2..20b7b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -23,12 +23,14 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.toPx
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
/**
* Distance over which the surface behind the keyguard is animated in during a Y-translation
@@ -96,13 +98,21 @@
.distinctUntilChanged()
/**
+ * Whether a notification launch animation is running when we're not already in the GONE state.
+ */
+ private val isNotificationLaunchAnimationRunningOnKeyguard =
+ notificationLaunchInteractor.isLaunchAnimationRunning
+ .sample(transitionInteractor.finishedKeyguardState)
+ .map { it != KeyguardState.GONE }
+
+ /**
* Whether we're animating the surface, or a notification launch animation is running (which
* means we're going to animate the surface, even if animators aren't yet running).
*/
val isAnimatingSurface =
combine(
repository.isAnimatingSurface,
- notificationLaunchInteractor.isLaunchAnimationRunning
+ isNotificationLaunchAnimationRunningOnKeyguard,
) { animatingSurface, animatingLaunch ->
animatingSurface || animatingLaunch
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index b5d6177..6b8e896 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -38,6 +38,7 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
import kotlin.math.max
@@ -217,15 +218,21 @@
if (!DEBUG || currentClock == null) return
val smallClockViewId = R.id.lockscreen_clock_view
val largeClockViewId = currentClock.largeClock.layout.views[0].id
+ val smartspaceDateId = sharedR.id.date_smartspace_view
Log.i(
TAG,
"applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
- "alpha=${cs.getConstraint(smallClockViewId).propertySet}"
+ "alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha}"
)
Log.i(
TAG,
"applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
- "alpha=${cs.getConstraint(largeClockViewId).propertySet}"
+ "alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha}"
+ )
+ Log.i(
+ TAG,
+ "applyCsToSmartspaceDate: vis=${cs.getVisibility(smartspaceDateId)} " +
+ "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 6255f0d..7178e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -36,7 +36,6 @@
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.shared.clocks.DEFAULT_CLOCK_ID
import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
@@ -76,13 +75,13 @@
}
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.clockShouldBeCentered.collect { clockShouldBeCentered ->
+ viewModel.clockShouldBeCentered.collect {
viewModel.currentClock.value?.let {
- // Weather clock also has hasCustomPositionUpdatedAnimation as true
- // TODO(b/323020908): remove ID check
+ // TODO(b/301502635): remove "!it.config.useCustomClockScene" when
+ // migrate clocks to blueprint is fully rolled out
if (
it.largeClock.config.hasCustomPositionUpdatedAnimation &&
- it.config.id == DEFAULT_CLOCK_ID
+ !it.config.useCustomClockScene
) {
blueprintInteractor.refreshBlueprint(Type.DefaultClockStepping)
} else {
@@ -93,12 +92,9 @@
}
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.isAodIconsVisible.collect { isAodIconsVisible ->
+ viewModel.isAodIconsVisible.collect {
viewModel.currentClock.value?.let {
- // Weather clock also has hasCustomPositionUpdatedAnimation as true
- if (
- viewModel.useLargeClock && it.config.id == "DIGITAL_CLOCK_WEATHER"
- ) {
+ if (viewModel.useLargeClock && it.config.useCustomClockScene) {
blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 5ee35e4f..cc54920 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -30,6 +30,9 @@
import android.view.ViewGroup.OnHierarchyChangeListener
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
@@ -49,6 +52,7 @@
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
@@ -125,6 +129,21 @@
disposables +=
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
+ if (ComposeLockscreen.isEnabled) {
+ view.setViewTreeOnBackPressedDispatcherOwner(
+ object : OnBackPressedDispatcherOwner {
+ override val onBackPressedDispatcher =
+ OnBackPressedDispatcher().apply {
+ setOnBackInvokedDispatcher(
+ view.viewRootImpl.onBackInvokedDispatcher
+ )
+ }
+
+ override val lifecycle: Lifecycle =
+ this@repeatWhenAttached.lifecycle
+ }
+ )
+ }
launch {
occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eaa5e33..9ec7a65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,7 +18,6 @@
import android.content.Context
import android.view.View
-import android.view.View.GONE
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
@@ -205,8 +204,7 @@
smartspaceController.requestSmartspaceUpdate()
constraintSet.apply {
- setVisibility(
- sharedR.id.weather_smartspace_view,
+ val weatherVisibility =
when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
true -> ConstraintSet.GONE
false ->
@@ -215,11 +213,18 @@
false -> ConstraintSet.GONE
}
}
+ setVisibility(sharedR.id.weather_smartspace_view, weatherVisibility)
+ setAlpha(
+ sharedR.id.weather_smartspace_view,
+ if (weatherVisibility == ConstraintSet.VISIBLE) 1f else 0f
)
- setVisibility(
- sharedR.id.date_smartspace_view,
+ val dateVisibility =
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
else ConstraintSet.VISIBLE
+ setVisibility(sharedR.id.date_smartspace_view, dateVisibility)
+ setAlpha(
+ sharedR.id.date_smartspace_view,
+ if (dateVisibility == ConstraintSet.VISIBLE) 1f else 0f
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 49fffdd..45dca99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -30,7 +30,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
@@ -64,7 +64,6 @@
transitionInteractor: KeyguardTransitionInteractor,
val keyguardInteractor: KeyguardInteractor,
val viewModel: AodToLockscreenTransitionViewModel,
- private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
@@ -242,7 +241,7 @@
}
suspend fun onLongPress() {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f6da033..a6d3312 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -118,8 +118,7 @@
currentClock
) { isLargeClockVisible, clockShouldBeCentered, shadeMode, currentClock ->
val shouldUseSplitShade = shadeMode == ShadeMode.Split
- // TODO(b/326098079): make id a constant field in config
- if (currentClock?.config?.id == "DIGITAL_CLOCK_WEATHER") {
+ if (currentClock?.config?.useCustomClockScene == true) {
val weatherClockLayout =
when {
shouldUseSplitShade && clockShouldBeCentered ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
new file mode 100644
index 0000000..cfe5cde
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/model/MediaSortKeyModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.data.model
+
+import com.android.internal.logging.InstanceId
+import com.android.systemui.media.controls.shared.model.MediaData.Companion.PLAYBACK_LOCAL
+
+data class MediaSortKeyModel(
+ /** Whether the item represents a Smartspace media recommendation that should be prioritized. */
+ val isPrioritizedRec: Boolean = false,
+ val isPlaying: Boolean? = null,
+ val playbackLocation: Int = PLAYBACK_LOCAL,
+ val active: Boolean = true,
+ val isResume: Boolean = false,
+ val lastActive: Long = 0L,
+ val notificationKey: String? = null,
+ val updateTime: Long = 0,
+ val instanceId: InstanceId? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index 9dc5900..7e57cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -18,10 +18,14 @@
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.data.model.MediaSortKeyModel
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.util.time.SystemClock
+import java.util.TreeMap
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,7 +33,7 @@
/** A repository that holds the state of filtered media data on the device. */
@SysUISingleton
-class MediaFilterRepository @Inject constructor() {
+class MediaFilterRepository @Inject constructor(private val systemClock: SystemClock) {
/** Instance id of media control that recommendations card reactivated. */
private val _reactivatedId: MutableStateFlow<InstanceId?> = MutableStateFlow(null)
@@ -58,6 +62,26 @@
val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
_recommendationsLoadingState.asStateFlow()
+ private val comparator =
+ compareByDescending<MediaSortKeyModel> {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_LOCAL
+ }
+ .thenByDescending {
+ it.isPlaying == true && it.playbackLocation == MediaData.PLAYBACK_CAST_LOCAL
+ }
+ .thenByDescending { it.active }
+ .thenByDescending { it.isPrioritizedRec }
+ .thenByDescending { !it.isResume }
+ .thenByDescending { it.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE }
+ .thenByDescending { it.lastActive }
+ .thenByDescending { it.updateTime }
+ .thenByDescending { it.notificationKey }
+
+ private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> =
+ MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator))
+ val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> =
+ _sortedMedia.asStateFlow()
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -138,9 +162,91 @@
} else {
emptyList()
}
+
+ addMediaLoadingToSortedMap(mediaDataLoadingModel)
}
- fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+ fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
_recommendationsLoadingState.value = smartspaceMediaLoadingModel
+
+ addRecsLoadingToSortedMap(smartspaceMediaLoadingModel)
+ }
+
+ private fun addMediaLoadingToSortedMap(mediaDataLoadingModel: MediaDataLoadingModel) {
+ val instanceId =
+ when (mediaDataLoadingModel) {
+ is MediaDataLoadingModel.Loaded -> mediaDataLoadingModel.instanceId
+ is MediaDataLoadingModel.Removed -> mediaDataLoadingModel.instanceId
+ MediaDataLoadingModel.Unknown -> null
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaControl ||
+ commonModel.instanceId != instanceId
+ }
+ )
+
+ _selectedUserEntries.value[instanceId]?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = false,
+ it.isPlaying,
+ it.playbackLocation,
+ it.active,
+ it.resumption,
+ it.lastActive,
+ it.notificationKey,
+ systemClock.currentTimeMillis(),
+ it.instanceId,
+ )
+
+ if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaControl(it.instanceId)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
+ }
+
+ private fun addRecsLoadingToSortedMap(
+ smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel
+ ) {
+ val isPrioritized: Boolean
+ val key: String?
+ when (smartspaceMediaLoadingModel) {
+ is SmartspaceMediaLoadingModel.Loaded -> {
+ isPrioritized = smartspaceMediaLoadingModel.isPrioritized
+ key = smartspaceMediaLoadingModel.key
+ }
+ is SmartspaceMediaLoadingModel.Removed -> {
+ isPrioritized = false
+ key = smartspaceMediaLoadingModel.key
+ }
+ SmartspaceMediaLoadingModel.Unknown -> {
+ isPrioritized = false
+ key = null
+ }
+ }
+ val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)
+ sortedMap.putAll(
+ _sortedMedia.value.filter { (_, commonModel) ->
+ commonModel !is MediaCommonModel.MediaRecommendations || commonModel.key != key
+ }
+ )
+
+ key?.let {
+ val sortKey =
+ MediaSortKeyModel(
+ isPrioritizedRec = isPrioritized,
+ isPlaying = false,
+ active = _smartspaceMediaData.value.isActive,
+ )
+ if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) {
+ sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(key)
+ }
+ }
+
+ _sortedMedia.value = sortedMap
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index a30e582..5432a18 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -69,7 +69,11 @@
private val mediaFlags: MediaFlags,
private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
- lateinit var mediaDataManager: MediaDataManager
+ /** Non-UI listeners to media changes. */
+ private val _listeners: MutableSet<MediaDataProcessor.Listener> = mutableSetOf()
+ val listeners: Set<MediaDataProcessor.Listener>
+ get() = _listeners.toSet()
+ lateinit var mediaDataProcessor: MediaDataProcessor
// Ensure the field (and associated reference) isn't removed during optimization.
@KeepForWeakReference
@@ -113,6 +117,9 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+
+ // Notify listeners
+ listeners.forEach { it.onMediaDataLoaded(key, oldKey, data) }
}
override fun onSmartspaceMediaDataLoaded(
@@ -171,6 +178,20 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(lastActiveId)
)
+ listeners.forEach { listener ->
+ getKey(lastActiveId)?.let { lastActiveKey ->
+ listener.onMediaDataLoaded(
+ lastActiveKey,
+ lastActiveKey,
+ mediaData,
+ receivedSmartspaceCardLatency =
+ (systemClock.currentTimeMillis() -
+ data.headphoneConnectionTimeMillis)
+ .toInt(),
+ isSsReactivated = true
+ )
+ }
+ }
}
} else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
@@ -186,9 +207,10 @@
smartspaceMediaData.packageName,
smartspaceMediaData.instanceId
)
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
)
+ listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) }
}
override fun onMediaDataRemoved(key: String) {
@@ -198,6 +220,8 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ // Only notify listeners if something actually changed
+ listeners.forEach { it.onMediaDataRemoved(key) }
}
}
}
@@ -212,6 +236,11 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(lastActiveId, immediately)
)
+ listeners.forEach { listener ->
+ getKey(lastActiveId)?.let { lastActiveKey ->
+ listener.onMediaDataLoaded(lastActiveKey, lastActiveKey, it, immediately)
+ }
+ }
}
}
@@ -224,9 +253,10 @@
)
)
}
- mediaFilterRepository.setRecommedationsLoadingState(
+ mediaFilterRepository.setRecommendationsLoadingState(
SmartspaceMediaLoadingModel.Removed(key, immediately)
)
+ listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
}
@VisibleForTesting
@@ -240,6 +270,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(data.instanceId)
)
+ listeners.forEach { listener -> listener.onMediaDataRemoved(key) }
}
}
}
@@ -247,6 +278,7 @@
@VisibleForTesting
internal fun handleUserSwitched() {
// If the user changes, remove all current MediaData objects.
+ val listenersCopy = listeners
val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
// Clear the list first and update loading state to remove media from UI.
mediaFilterRepository.clearSelectedUserMedia()
@@ -255,6 +287,9 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Removed(instanceId)
)
+ getKey(instanceId)?.let {
+ listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) }
+ }
}
mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
@@ -268,6 +303,7 @@
mediaFilterRepository.addMediaDataLoadingState(
MediaDataLoadingModel.Loaded(data.instanceId)
)
+ listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) }
}
}
}
@@ -279,7 +315,7 @@
mediaEntries.forEach { (key, data) ->
if (mediaFilterRepository.selectedUserEntries.value.containsKey(data.instanceId)) {
// Force updates to listeners, needed for re-activated card
- mediaDataManager.setInactive(key, timedOut = true, forceUpdate = true)
+ mediaDataProcessor.setInactive(key, timedOut = true, forceUpdate = true)
}
}
val smartspaceMediaData = mediaFilterRepository.smartspaceMediaData.value
@@ -301,7 +337,7 @@
if (mediaFlags.isPersistentSsCardEnabled()) {
mediaFilterRepository.setRecommendation(smartspaceMediaData.copy(isActive = false))
- mediaDataManager.setRecommendationInactive(smartspaceMediaData.targetId)
+ mediaDataProcessor.setRecommendationInactive(smartspaceMediaData.targetId)
} else {
mediaFilterRepository.setRecommendation(
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
@@ -309,7 +345,7 @@
instanceId = smartspaceMediaData.instanceId,
)
)
- mediaDataManager.dismissSmartspaceRecommendation(
+ mediaDataProcessor.dismissSmartspaceRecommendation(
smartspaceMediaData.targetId,
delay = 0L,
)
@@ -317,6 +353,12 @@
}
}
+ /** Add a listener for filtered [MediaData] changes */
+ fun addListener(listener: MediaDataProcessor.Listener) = _listeners.add(listener)
+
+ /** Remove a listener that was registered with addListener */
+ fun removeListener(listener: MediaDataProcessor.Listener) = _listeners.remove(listener)
+
/**
* Return the time since last active for the most-recent media.
*
@@ -336,6 +378,16 @@
return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
}
+ private fun getKey(instanceId: InstanceId): String? {
+ val allEntries = mediaFilterRepository.allUserEntries.value
+ val filteredEntries = allEntries.filter { (_, data) -> data.instanceId == instanceId }
+ return if (filteredEntries.isNotEmpty()) {
+ filteredEntries.keys.first()
+ } else {
+ null
+ }
+ }
+
companion object {
/**
* Maximum age of a media control to re-activate on smartspace signal. If there is no media
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index cdcf363..b04e938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -34,8 +34,10 @@
import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.media.controls.util.MediaControlsRefactorFlag
import com.android.systemui.media.controls.util.MediaFlags
import java.io.PrintWriter
import javax.inject.Inject
@@ -46,6 +48,7 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
@@ -120,6 +123,10 @@
val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
mediaFilterRepository.recommendationsLoadingState
+ /** The most recent sorted set for user media instances */
+ val sortedMedia: Flow<List<MediaCommonModel>> =
+ mediaFilterRepository.sortedMedia.map { it.values.toList() }
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
@@ -150,13 +157,19 @@
mediaDataProcessor.onSessionDestroyed(key)
}
mediaResumeListener.setManager(this)
- mediaDataFilter.mediaDataManager = this
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
}
- override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) {
- mediaDataProcessor.setInactive(key, timedOut, forceUpdate)
+ override fun addListener(listener: MediaDataManager.Listener) {
+ mediaDataFilter.addListener(listener)
}
+ override fun removeListener(listener: MediaDataManager.Listener) {
+ mediaDataFilter.removeListener(listener)
+ }
+
+ override fun setInactive(key: String, timedOut: Boolean, forceUpdate: Boolean) = unsupported
+
override fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
mediaDataProcessor.onNotificationAdded(key, sbn)
}
@@ -201,9 +214,7 @@
return mediaDataProcessor.dismissSmartspaceRecommendation(key, delay)
}
- override fun setRecommendationInactive(key: String) {
- mediaDataProcessor.setRecommendationInactive(key)
- }
+ override fun setRecommendationInactive(key: String) = unsupported
override fun onNotificationRemoved(key: String) {
mediaDataProcessor.onNotificationRemoved(key)
@@ -234,4 +245,12 @@
override fun dump(pw: PrintWriter, args: Array<out String>) {
mediaDeviceManager.dump(pw)
}
+
+ companion object {
+ val unsupported: Nothing
+ get() =
+ error(
+ "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled"
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
new file mode 100644
index 0000000..83e2765
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models any type of media. */
+sealed class MediaCommonModel {
+ data class MediaControl(val instanceId: InstanceId) : MediaCommonModel()
+
+ data class MediaRecommendations(val key: String) : MediaCommonModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
new file mode 100644
index 0000000..14a9179
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.widget.ImageButton
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.res.R
+
+object MediaControlViewBinder {
+
+ fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+ setVisibleAndAlpha(set, resId, visible, ConstraintSet.GONE)
+ }
+
+ private fun setVisibleAndAlpha(
+ set: ConstraintSet,
+ resId: Int,
+ visible: Boolean,
+ notVisibleValue: Int
+ ) {
+ set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else notVisibleValue)
+ set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+ }
+
+ fun updateSeekBarVisibility(constraintSet: ConstraintSet, isSeekBarEnabled: Boolean) {
+ if (isSeekBarEnabled) {
+ constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.VISIBLE)
+ constraintSet.setAlpha(R.id.media_progress_bar, 1.0f)
+ } else {
+ constraintSet.setVisibility(R.id.media_progress_bar, ConstraintSet.INVISIBLE)
+ constraintSet.setAlpha(R.id.media_progress_bar, 0.0f)
+ }
+ }
+
+ fun setSemanticButtonVisibleAndAlpha(
+ button: ImageButton,
+ expandedSet: ConstraintSet,
+ collapsedSet: ConstraintSet,
+ visible: Boolean,
+ notVisibleValue: Int,
+ showInCollapsed: Boolean
+ ) {
+ if (notVisibleValue == ConstraintSet.INVISIBLE) {
+ // Since time views should appear instead of buttons.
+ button.isFocusable = visible
+ button.isClickable = visible
+ }
+ setVisibleAndAlpha(expandedSet, button.id, visible, notVisibleValue)
+ setVisibleAndAlpha(collapsedSet, button.id, visible = visible && showInCollapsed)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
new file mode 100644
index 0000000..9c6d59e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaRecommendationsViewBinder.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.ui.binder
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.content.res.Configuration
+import android.graphics.Matrix
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
+import com.android.systemui.media.controls.ui.controller.MediaViewController
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecommendationsViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaRecsCardViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
+import com.android.systemui.util.animation.TransitionLayout
+import kotlin.math.min
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+object MediaRecommendationsViewBinder {
+
+ /** Binds recommendations view holder to the given view-model */
+ fun bind(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecommendationsViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ mediaViewController.recsConfigurationChangeListener = this::updateRecommendationsVisibility
+ val cardView = viewHolder.recommendations
+ cardView.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.mediaRecsCard.collectLatest { viewModel ->
+ viewModel?.let {
+ bindRecsCard(viewHolder, it, mediaViewController, falsingManager)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun bindRecsCard(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ // Bind main card.
+ viewHolder.cardTitle.setTextColor(viewModel.cardTitleColor)
+ viewHolder.recommendations.backgroundTintList = ColorStateList.valueOf(viewModel.cardColor)
+ viewHolder.recommendations.contentDescription =
+ viewModel.contentDescription.invoke(mediaViewController.isGutsVisible)
+
+ viewHolder.recommendations.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ viewModel.onClicked(Expandable.fromView(it))
+ }
+
+ viewHolder.recommendations.setOnLongClickListener {
+ if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+ return@setOnLongClickListener true
+ if (!mediaViewController.isGutsVisible) {
+ openGuts(viewHolder, viewModel, mediaViewController)
+ } else {
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ }
+ return@setOnLongClickListener true
+ }
+
+ // Bind all recommendations.
+ bindRecommendationsList(viewHolder, viewModel.mediaRecs, falsingManager)
+ updateRecommendationsVisibility(mediaViewController, viewHolder.recommendations)
+
+ // Set visibility of recommendations.
+ val expandedSet: ConstraintSet = mediaViewController.expandedLayout
+ val collapsedSet: ConstraintSet = mediaViewController.collapsedLayout
+ viewHolder.mediaTitles.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, viewModel.areTitlesVisible)
+ setVisibleAndAlpha(collapsedSet, it.id, viewModel.areTitlesVisible)
+ }
+ viewHolder.mediaSubtitles.forEach {
+ setVisibleAndAlpha(expandedSet, it.id, viewModel.areSubtitlesVisible)
+ setVisibleAndAlpha(collapsedSet, it.id, viewModel.areSubtitlesVisible)
+ }
+
+ bindRecommendationsGuts(viewHolder, viewModel, mediaViewController, falsingManager)
+
+ mediaViewController.refreshState()
+ }
+
+ private fun bindRecommendationsGuts(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ falsingManager: FalsingManager,
+ ) {
+ val gutsViewHolder = viewHolder.gutsViewHolder
+ val gutsViewModel = viewModel.gutsMenu
+
+ gutsViewHolder.gutsText.text = gutsViewModel.gutsText
+ gutsViewHolder.dismissText.visibility = View.VISIBLE
+ gutsViewHolder.dismiss.isEnabled = true
+ gutsViewHolder.dismiss.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ gutsViewModel.onDismissClicked.invoke()
+ }
+
+ gutsViewHolder.cancelText.background = gutsViewModel.cancelTextBackground
+ gutsViewHolder.cancel.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ closeGuts(viewHolder, viewModel, mediaViewController)
+ }
+ }
+
+ gutsViewHolder.settings.setOnClickListener {
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ gutsViewModel.onSettingsClicked.invoke()
+ }
+ }
+
+ gutsViewHolder.setDismissible(gutsViewModel.isDismissEnabled)
+ gutsViewHolder.setTextPrimaryColor(gutsViewModel.textPrimaryColor)
+ gutsViewHolder.setAccentPrimaryColor(gutsViewModel.accentPrimaryColor)
+ gutsViewHolder.setSurfaceColor(gutsViewModel.surfaceColor)
+ }
+
+ private fun bindRecommendationsList(
+ viewHolder: RecommendationViewHolder,
+ mediaRecs: List<MediaRecViewModel>,
+ falsingManager: FalsingManager
+ ) {
+ mediaRecs.forEachIndexed { index, mediaRecViewModel ->
+ if (index >= NUM_REQUIRED_RECOMMENDATIONS) return@forEachIndexed
+
+ val appIconView = viewHolder.mediaAppIcons[index]
+ appIconView.clearColorFilter()
+ if (mediaRecViewModel.appIcon != null) {
+ appIconView.setImageDrawable(mediaRecViewModel.appIcon)
+ } else {
+ appIconView.setImageResource(R.drawable.ic_music_note)
+ }
+
+ val mediaCoverContainer = viewHolder.mediaCoverContainers[index]
+ mediaCoverContainer.setOnClickListener {
+ if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) return@setOnClickListener
+ mediaRecViewModel.onClicked.invoke(Expandable.fromView(it), index)
+ }
+ mediaCoverContainer.setOnLongClickListener {
+ if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY))
+ return@setOnLongClickListener true
+ (it.parent as View).performLongClick()
+ return@setOnLongClickListener true
+ }
+
+ val mediaCover = viewHolder.mediaCoverItems[index]
+ val width: Int =
+ mediaCover.context.resources.getDimensionPixelSize(R.dimen.qs_media_rec_album_width)
+ val height: Int =
+ mediaCover.context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_rec_album_height_expanded
+ )
+ val coverMatrix = Matrix(mediaCover.imageMatrix)
+ coverMatrix.postScale(1.25f, 1.25f, 0.5f * width, 0.5f * height)
+ mediaCover.imageMatrix = coverMatrix
+ mediaCover.setImageDrawable(mediaRecViewModel.albumIcon)
+ mediaCover.contentDescription = mediaRecViewModel.contentDescription
+
+ val title = viewHolder.mediaTitles[index]
+ title.text = mediaRecViewModel.title
+ title.setTextColor(ColorStateList.valueOf(mediaRecViewModel.titleColor))
+
+ val subtitle = viewHolder.mediaSubtitles[index]
+ subtitle.text = mediaRecViewModel.subtitle
+ subtitle.setTextColor(ColorStateList.valueOf(mediaRecViewModel.subtitleColor))
+
+ val progressBar = viewHolder.mediaProgressBars[index]
+ progressBar.progress = mediaRecViewModel.progress
+ progressBar.progressTintList = ColorStateList.valueOf(mediaRecViewModel.progressColor)
+ if (mediaRecViewModel.progress == 0) {
+ progressBar.visibility = View.GONE
+ }
+ }
+ }
+
+ private fun openGuts(
+ viewHolder: RecommendationViewHolder,
+ viewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ ) {
+ viewHolder.marquee(true, MediaViewController.GUTS_ANIMATION_DURATION)
+ mediaViewController.openGuts()
+ viewHolder.recommendations.contentDescription = viewModel.contentDescription.invoke(true)
+ viewModel.onLongClicked.invoke()
+ }
+
+ private fun closeGuts(
+ viewHolder: RecommendationViewHolder,
+ mediaRecsCardViewModel: MediaRecsCardViewModel,
+ mediaViewController: MediaViewController,
+ ) {
+ viewHolder.marquee(false, MediaViewController.GUTS_ANIMATION_DURATION)
+ mediaViewController.closeGuts(false)
+ viewHolder.recommendations.contentDescription =
+ mediaRecsCardViewModel.contentDescription.invoke(false)
+ }
+
+ private fun setVisibleAndAlpha(set: ConstraintSet, resId: Int, visible: Boolean) {
+ set.setVisibility(resId, if (visible) ConstraintSet.VISIBLE else ConstraintSet.GONE)
+ set.setAlpha(resId, if (visible) 1.0f else 0.0f)
+ }
+
+ private fun updateRecommendationsVisibility(
+ mediaViewController: MediaViewController,
+ cardView: TransitionLayout,
+ ) {
+ val fittedRecsNum = getNumberOfFittedRecommendations(cardView.context)
+ val expandedSet = mediaViewController.expandedLayout
+ val collapsedSet = mediaViewController.collapsedLayout
+ val mediaCoverContainers = getMediaCoverContainers(cardView)
+ // Hide media cover that cannot fit in the recommendation card.
+ mediaCoverContainers.forEachIndexed { index, container ->
+ setVisibleAndAlpha(expandedSet, container.id, index < fittedRecsNum)
+ setVisibleAndAlpha(collapsedSet, container.id, index < fittedRecsNum)
+ }
+ }
+
+ private fun getMediaCoverContainers(cardView: TransitionLayout): List<ViewGroup> {
+ return listOf<ViewGroup>(
+ cardView.requireViewById(R.id.media_cover1_container),
+ cardView.requireViewById(R.id.media_cover2_container),
+ cardView.requireViewById(R.id.media_cover3_container),
+ )
+ }
+
+ private fun getNumberOfFittedRecommendations(context: Context): Int {
+ val res = context.resources
+ val config = res.configuration
+ val defaultDpWidth = res.getInteger(R.integer.default_qs_media_rec_width_dp)
+ val recCoverWidth =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+
+ // On landscape, media controls should take half of the screen width.
+ val displayAvailableDpWidth =
+ if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ config.screenWidthDp / 2
+ } else {
+ config.screenWidthDp
+ }
+ val fittedNum =
+ if (displayAvailableDpWidth > defaultDpWidth) {
+ val recCoverDefaultWidth =
+ res.getDimensionPixelSize(R.dimen.qs_media_rec_default_width)
+ recCoverDefaultWidth / recCoverWidth
+ } else {
+ val displayAvailableWidth =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ displayAvailableDpWidth.toFloat(),
+ res.displayMetrics
+ )
+ .toInt()
+ displayAvailableWidth / recCoverWidth
+ }
+ return min(fittedNum.toDouble(), NUM_REQUIRED_RECOMMENDATIONS.toDouble()).toInt()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 1a56a9b..bd3893b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -738,8 +738,11 @@
mPackageName, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- mMediaOutputDialogManager.createAndShow(mPackageName, true,
- mMediaViewHolder.getSeamlessButton());
+ // TODO: b/321969740 - Populate the userHandle parameter. The user
+ // handle is necessary to disambiguate the same package running on
+ // different users.
+ mMediaOutputDialogManager.createAndShow(
+ mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
}
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
@@ -767,8 +770,11 @@
Log.w(TAG, "Device pending intent is not an activity.");
}
} else {
- mMediaOutputDialogManager.createAndShow(mPackageName, true,
- mMediaViewHolder.getSeamlessButton());
+ // TODO: b/321969740 - Populate the userHandle parameter. The user
+ // handle is necessary to disambiguate the same package running on
+ // different users.
+ mMediaOutputDialogManager.createAndShow(
+ mPackageName, true, mMediaViewHolder.getSeamlessButton(), null);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index ad7990b..7fced5f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -16,41 +16,73 @@
package com.android.systemui.media.controls.ui.controller
+import android.animation.Animator
+import android.animation.AnimatorInflater
+import android.animation.AnimatorSet
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Drawable
+import android.provider.Settings
+import android.view.View
+import android.view.animation.Interpolator
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import com.android.app.animation.Interpolators
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition
+import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler
+import com.android.systemui.media.controls.ui.binder.MediaControlViewBinder
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver
import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.surfaceeffects.PaintDrawCallback
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionLayoutController
import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.settings.GlobalSettings
import java.lang.Float.max
import java.lang.Float.min
+import java.util.Random
import javax.inject.Inject
/**
* A class responsible for controlling a single instance of a media player handling interactions
* with the view instance and keeping the media view states up to date.
*/
-class MediaViewController
+open class MediaViewController
@Inject
constructor(
private val context: Context,
private val configurationController: ConfigurationController,
private val mediaHostStatesManager: MediaHostStatesManager,
private val logger: MediaViewLogger,
+ private val seekBarViewModel: SeekBarViewModel,
+ @Main private val mainExecutor: DelayableExecutor,
private val mediaFlags: MediaFlags,
+ private val globalSettings: GlobalSettings,
) {
/**
@@ -69,6 +101,7 @@
/** A listener when the current dimensions of the player change */
lateinit var sizeChangedListener: () -> Unit
lateinit var configurationChangeListener: () -> Unit
+ lateinit var recsConfigurationChangeListener: (MediaViewController, TransitionLayout) -> Unit
private var firstRefresh: Boolean = true
@VisibleForTesting private var transitionLayout: TransitionLayout? = null
private val layoutController = TransitionLayoutController()
@@ -130,6 +163,72 @@
return transitionLayout?.translationY ?: 0.0f
}
+ /** Whether artwork is bound. */
+ var isArtworkBound: Boolean = false
+
+ /** previous background artwork */
+ var prevArtwork: Drawable? = null
+
+ /** Whether scrubbing time can show */
+ var canShowScrubbingTime: Boolean = false
+
+ /** Whether user is touching the seek bar to change the position */
+ var isScrubbing: Boolean = false
+
+ var isSeekBarEnabled: Boolean = false
+
+ /** Not visible value for previous button when scrubbing */
+ private var prevNotVisibleValue = ConstraintSet.GONE
+ private var isPrevButtonAvailable = false
+
+ /** Not visible value for next button when scrubbing */
+ private var nextNotVisibleValue = ConstraintSet.GONE
+ private var isNextButtonAvailable = false
+
+ private lateinit var mediaViewHolder: MediaViewHolder
+ private lateinit var seekBarObserver: SeekBarObserver
+ private lateinit var turbulenceNoiseController: TurbulenceNoiseController
+ private lateinit var loadingEffect: LoadingEffect
+ private lateinit var turbulenceNoiseAnimationConfig: TurbulenceNoiseAnimationConfig
+ private lateinit var noiseDrawCallback: PaintDrawCallback
+ private lateinit var stateChangedCallback: LoadingEffect.AnimationStateChangedCallback
+ internal lateinit var metadataAnimationHandler: MetadataAnimationHandler
+ internal lateinit var colorSchemeTransition: ColorSchemeTransition
+ internal lateinit var multiRippleController: MultiRippleController
+
+ private val scrubbingChangeListener =
+ object : SeekBarViewModel.ScrubbingChangeListener {
+ override fun onScrubbingChanged(scrubbing: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (isScrubbing == scrubbing) return
+ isScrubbing = scrubbing
+ updateDisplayForScrubbingChange()
+ }
+ }
+
+ private val enabledChangeListener =
+ object : SeekBarViewModel.EnabledChangeListener {
+ override fun onEnabledChanged(enabled: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (isSeekBarEnabled == enabled) return
+ isSeekBarEnabled = enabled
+ MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
+ }
+ }
+
+ /**
+ * Sets the listening state of the player.
+ *
+ * Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
+ * unnecessary work when the QS panel is closed.
+ *
+ * @param listening True when player should be active. Otherwise, false.
+ */
+ fun setListening(listening: Boolean) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ seekBarViewModel.listening = listening
+ }
+
/** A callback for config changes */
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
@@ -160,7 +259,17 @@
)
)
}
- if (this@MediaViewController::configurationChangeListener.isInitialized) {
+ if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (
+ this@MediaViewController::recsConfigurationChangeListener.isInitialized
+ ) {
+ transitionLayout?.let {
+ recsConfigurationChangeListener.invoke(this@MediaViewController, it)
+ }
+ }
+ } else if (
+ this@MediaViewController::configurationChangeListener.isInitialized
+ ) {
configurationChangeListener.invoke()
refreshState()
}
@@ -221,6 +330,14 @@
* Notify this controller that the view has been removed and all listeners should be destroyed
*/
fun onDestroy() {
+ if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (this::seekBarObserver.isInitialized) {
+ seekBarViewModel.progress.removeObserver(seekBarObserver)
+ }
+ seekBarViewModel.removeScrubbingChangeListener(scrubbingChangeListener)
+ seekBarViewModel.removeEnabledChangeListener(enabledChangeListener)
+ seekBarViewModel.onDestroy()
+ }
mediaHostStatesManager.removeController(this)
configurationController.removeCallback(configurationListener)
}
@@ -535,6 +652,178 @@
)
}
+ fun attachPlayer(mediaViewHolder: MediaViewHolder) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ this.mediaViewHolder = mediaViewHolder
+
+ // Setting up seek bar.
+ seekBarObserver = SeekBarObserver(mediaViewHolder)
+ seekBarViewModel.progress.observeForever(seekBarObserver)
+ seekBarViewModel.attachTouchHandlers(mediaViewHolder.seekBar)
+ seekBarViewModel.setScrubbingChangeListener(scrubbingChangeListener)
+ seekBarViewModel.setEnabledChangeListener(enabledChangeListener)
+
+ val mediaCard = mediaViewHolder.player
+ attach(mediaViewHolder.player, TYPE.PLAYER)
+
+ val turbulenceNoiseView = mediaViewHolder.turbulenceNoiseView
+ turbulenceNoiseController = TurbulenceNoiseController(turbulenceNoiseView)
+
+ multiRippleController = MultiRippleController(mediaViewHolder.multiRippleView)
+
+ // Metadata Animation
+ val titleText = mediaViewHolder.titleText
+ val artistText = mediaViewHolder.artistText
+ val explicitIndicator = mediaViewHolder.explicitIndicator
+ val enter =
+ loadAnimator(
+ mediaCard.context,
+ R.anim.media_metadata_enter,
+ Interpolators.EMPHASIZED_DECELERATE,
+ titleText,
+ artistText,
+ explicitIndicator
+ )
+ val exit =
+ loadAnimator(
+ mediaCard.context,
+ R.anim.media_metadata_exit,
+ Interpolators.EMPHASIZED_ACCELERATE,
+ titleText,
+ artistText,
+ explicitIndicator
+ )
+ metadataAnimationHandler = MetadataAnimationHandler(exit, enter)
+
+ colorSchemeTransition =
+ ColorSchemeTransition(
+ mediaCard.context,
+ mediaViewHolder,
+ multiRippleController,
+ turbulenceNoiseController
+ )
+
+ // For Turbulence noise.
+ val loadingEffectView = mediaViewHolder.loadingEffectView
+ turbulenceNoiseAnimationConfig =
+ createTurbulenceNoiseConfig(
+ loadingEffectView,
+ turbulenceNoiseView,
+ colorSchemeTransition
+ )
+ noiseDrawCallback =
+ object : PaintDrawCallback {
+ override fun onDraw(paint: Paint) {
+ loadingEffectView.draw(paint)
+ }
+ }
+ stateChangedCallback =
+ object : LoadingEffect.AnimationStateChangedCallback {
+ override fun onStateChanged(
+ oldState: LoadingEffect.AnimationState,
+ newState: LoadingEffect.AnimationState
+ ) {
+ if (newState === LoadingEffect.AnimationState.NOT_PLAYING) {
+ loadingEffectView.visibility = View.INVISIBLE
+ } else {
+ loadingEffectView.visibility = View.VISIBLE
+ }
+ }
+ }
+ }
+
+ fun updateAnimatorDurationScale() {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (this::seekBarObserver.isInitialized) {
+ seekBarObserver.animationEnabled =
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
+ }
+ }
+
+ /** update view with the needed UI changes when user touches seekbar. */
+ private fun updateDisplayForScrubbingChange() {
+ mainExecutor.execute {
+ val isTimeVisible = canShowScrubbingTime && isScrubbing
+ MediaControlViewBinder.setVisibleAndAlpha(
+ expandedLayout,
+ mediaViewHolder.scrubbingTotalTimeView.id,
+ isTimeVisible
+ )
+ MediaControlViewBinder.setVisibleAndAlpha(
+ expandedLayout,
+ mediaViewHolder.scrubbingElapsedTimeView.id,
+ isTimeVisible
+ )
+
+ MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id ->
+ val isButtonVisible: Boolean
+ val notVisibleValue: Int
+ when (id) {
+ R.id.actionPrev -> {
+ isButtonVisible = isPrevButtonAvailable && !isTimeVisible
+ notVisibleValue = prevNotVisibleValue
+ }
+ R.id.actionNext -> {
+ isButtonVisible = isNextButtonAvailable && !isTimeVisible
+ notVisibleValue = nextNotVisibleValue
+ }
+ else -> {
+ isButtonVisible = !isTimeVisible
+ notVisibleValue = ConstraintSet.GONE
+ }
+ }
+ MediaControlViewBinder.setSemanticButtonVisibleAndAlpha(
+ mediaViewHolder.getAction(id),
+ expandedLayout,
+ collapsedLayout,
+ isButtonVisible,
+ notVisibleValue,
+ showInCollapsed = true
+ )
+ }
+
+ if (!metadataAnimationHandler.isRunning) {
+ refreshState()
+ }
+ }
+ }
+
+ fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ seekBarViewModel.logSeek = onSeek
+ onBindSeekBar.invoke(seekBarViewModel)
+ }
+
+ fun setUpTurbulenceNoise() {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (Flags.shaderlibLoadingEffectRefactor()) {
+ if (!this::loadingEffect.isInitialized) {
+ loadingEffect =
+ LoadingEffect(
+ TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+ turbulenceNoiseAnimationConfig,
+ noiseDrawCallback,
+ stateChangedCallback
+ )
+ }
+ colorSchemeTransition.loadingEffect = loadingEffect
+ loadingEffect.play()
+ mainExecutor.executeDelayed(
+ loadingEffect::finish,
+ MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+ )
+ } else {
+ turbulenceNoiseController.play(
+ TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+ turbulenceNoiseAnimationConfig
+ )
+ mainExecutor.executeDelayed(
+ turbulenceNoiseController::finish,
+ MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+ )
+ }
+ }
+
/**
* Obtain a measurement for a given location. This makes sure that the state is up to date and
* all widgets know their location. Calling this method may create a measurement if we don't
@@ -790,6 +1079,75 @@
applyImmediately = true
)
}
+
+ @VisibleForTesting
+ protected open fun loadAnimator(
+ context: Context,
+ animId: Int,
+ motionInterpolator: Interpolator?,
+ vararg targets: View?
+ ): AnimatorSet {
+ val animators = ArrayList<Animator>()
+ for (target in targets) {
+ val animator = AnimatorInflater.loadAnimator(context, animId) as AnimatorSet
+ animator.childAnimations[0].interpolator = motionInterpolator
+ animator.setTarget(target)
+ animators.add(animator)
+ }
+ val result = AnimatorSet()
+ result.playTogether(animators)
+ return result
+ }
+
+ private fun createTurbulenceNoiseConfig(
+ loadingEffectView: LoadingEffectView,
+ turbulenceNoiseView: TurbulenceNoiseView,
+ colorSchemeTransition: ColorSchemeTransition
+ ): TurbulenceNoiseAnimationConfig {
+ val targetView: View =
+ if (Flags.shaderlibLoadingEffectRefactor()) {
+ loadingEffectView
+ } else {
+ turbulenceNoiseView
+ }
+ val width = targetView.width
+ val height = targetView.height
+ val random = Random()
+ return TurbulenceNoiseAnimationConfig(
+ gridCount = 2.14f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
+ random.nextFloat(),
+ random.nextFloat(),
+ random.nextFloat(),
+ noiseMoveSpeedX = 0.42f,
+ noiseMoveSpeedY = 0f,
+ TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
+ // Color will be correctly updated in ColorSchemeTransition.
+ colorSchemeTransition.accentPrimary.currentColor,
+ screenColor = Color.BLACK,
+ width.toFloat(),
+ height.toFloat(),
+ TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
+ easeInDuration = 1350f,
+ easeOutDuration = 1350f,
+ targetView.context.resources.displayMetrics.density,
+ lumaMatteBlendFactor = 0.26f,
+ lumaMatteOverallBrightness = 0.09f,
+ shouldInverseNoiseLuminosity = false
+ )
+ }
+
+ fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ isPrevButtonAvailable = isAvailable
+ prevNotVisibleValue = notVisibleValue
+ }
+
+ fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
+ if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ isNextButtonAvailable = isAvailable
+ nextNotVisibleValue = notVisibleValue
+ }
}
/** An internal key for the cache of mediaViewStates. This is a subset of the full host state. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
index e508e1b..6c7c31c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/GutsViewModel.kt
@@ -22,9 +22,9 @@
/** Models UI state for media guts menu */
data class GutsViewModel(
val gutsText: CharSequence,
- @ColorInt val textColor: Int,
- @ColorInt val buttonBackgroundColor: Int,
- @ColorInt val buttonTextColor: Int,
+ @ColorInt val textPrimaryColor: Int,
+ @ColorInt val accentPrimaryColor: Int,
+ @ColorInt val surfaceColor: Int,
val isDismissEnabled: Boolean = true,
val onDismissClicked: () -> Unit,
val cancelTextBackground: Drawable?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
index 1e67a77..82099e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaActionViewModel.kt
@@ -24,7 +24,8 @@
val icon: Drawable?,
val contentDescription: CharSequence?,
val background: Drawable?,
- val isVisible: Boolean = true,
+ /** whether action is visible if user is touching seekbar to change position. */
+ val isVisibleWhenScrubbing: Boolean = true,
val notVisibleValue: Int = ConstraintSet.GONE,
val showInCollapsed: Boolean,
val rebindId: Int? = null,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 117b2af..d74506d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.content.pm.PackageManager
+import android.media.session.MediaController
import android.media.session.MediaSession.Token
import android.text.TextUtils
import android.util.Log
@@ -40,6 +41,7 @@
import com.android.systemui.monet.Style
import com.android.systemui.res.R
import com.android.systemui.util.kotlin.sample
+import java.util.concurrent.Executor
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,6 +53,7 @@
class MediaControlViewModel(
@Application private val applicationContext: Context,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundExecutor: Executor,
private val interactor: MediaControlInteractor,
private val logger: MediaUiEventLogger,
) {
@@ -124,13 +127,15 @@
}
},
backgroundCover = model.artwork,
- appIcon = getAppIcon(model.appIcon, model.isResume, model.packageName),
+ appIcon = model.appIcon,
+ launcherIcon = getIconFromApp(model.packageName),
useGrayColorFilter = model.appIcon == null || model.isResume,
artistName = model.artistName ?: "",
titleName = model.songName ?: "",
isExplicitVisible = model.showExplicit,
+ shouldAddGradient = wallpaperColors != null,
colorScheme = scheme,
- isTimeVisible = canShowScrubbingTimeViews(model.semanticActionButtons),
+ canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons),
playTurbulenceNoise = playTurbulenceNoise,
useSemanticActions = model.semanticActionButtons != null,
actionButtons = toActionViewModels(model),
@@ -146,6 +151,21 @@
onLongClicked = {
logger.logLongPressOpen(model.uid, model.packageName, model.instanceId)
},
+ onSeek = {
+ logger.logSeek(model.uid, model.packageName, model.instanceId)
+ // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT)
+ },
+ onBindSeekbar = { seekBarViewModel ->
+ if (model.isResume && model.resumeProgress != null) {
+ seekBarViewModel.updateStaticProgress(model.resumeProgress)
+ } else {
+ backgroundExecutor.execute {
+ seekBarViewModel.updateController(
+ model.token?.let { MediaController(applicationContext, it) }
+ )
+ }
+ }
+ }
)
}
@@ -235,9 +255,9 @@
} else {
applicationContext.getString(R.string.controls_media_active_session)
},
- textColor = textPrimaryFromScheme(scheme),
- buttonBackgroundColor = accentPrimaryFromScheme(scheme),
- buttonTextColor = surfaceFromScheme(scheme),
+ textPrimaryColor = textPrimaryFromScheme(scheme),
+ accentPrimaryColor = accentPrimaryFromScheme(scheme),
+ surfaceColor = surfaceFromScheme(scheme),
isDismissEnabled = model.isDismissible,
onDismissClicked = {
onDismissMediaData(model.token, model.uid, model.packageName, model.instanceId)
@@ -278,16 +298,16 @@
model: MediaControlModel,
mediaAction: MediaAction,
buttonId: Int,
- isScrubbingTimeEnabled: Boolean
+ canShowScrubbingTimeViews: Boolean
): MediaActionViewModel {
val showInCollapsed = SEMANTIC_ACTIONS_COMPACT.contains(buttonId)
val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId)
- val shouldHideDueToScrubbing = isScrubbingTimeEnabled && hideWhenScrubbing
+ val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing
return MediaActionViewModel(
icon = mediaAction.icon,
contentDescription = mediaAction.contentDescription,
background = mediaAction.background,
- isVisible = !shouldHideDueToScrubbing,
+ isVisibleWhenScrubbing = !shouldHideWhenScrubbing,
notVisibleValue =
if (
(buttonId == R.id.actionPrev && model.semanticActionButtons!!.reservePrev) ||
@@ -342,19 +362,6 @@
action.run()
}
- private fun getAppIcon(
- icon: android.graphics.drawable.Icon?,
- isResume: Boolean,
- packageName: String
- ): Icon {
- if (icon != null && !isResume) {
- icon.loadDrawable(applicationContext)?.let { drawable ->
- return Icon.Loaded(drawable, null)
- }
- }
- return getIconFromApp(packageName)
- }
-
private fun getIconFromApp(packageName: String): Icon {
return try {
Icon.Loaded(applicationContext.packageManager.getApplicationIcon(packageName), null)
@@ -381,17 +388,17 @@
private const val DISABLED_ALPHA = 0.38f
/** Buttons to show in small player when using semantic actions */
- private val SEMANTIC_ACTIONS_COMPACT =
+ val SEMANTIC_ACTIONS_COMPACT =
listOf(R.id.actionPlayPause, R.id.actionPrev, R.id.actionNext)
/**
* Buttons that should get hidden when we are scrubbing (they will be replaced with the
* views showing scrubbing time)
*/
- private val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
+ val SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING = listOf(R.id.actionPrev, R.id.actionNext)
/** Buttons to show in player when using semantic actions. */
- private val SEMANTIC_ACTIONS_ALL =
+ val SEMANTIC_ACTIONS_ALL =
listOf(
R.id.actionPlayPause,
R.id.actionPrev,
@@ -399,5 +406,9 @@
R.id.action0,
R.id.action1
)
+
+ const val TURBULENCE_NOISE_PLAY_MS_DURATION = 7500L
+ const val MEDIA_PLAYER_SCRIM_START_ALPHA = 0.25f
+ const val MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
index 9029a65..d1014e8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt
@@ -24,13 +24,15 @@
data class MediaPlayerViewModel(
val contentDescription: (Boolean) -> CharSequence,
val backgroundCover: android.graphics.drawable.Icon?,
- val appIcon: Icon,
+ val appIcon: android.graphics.drawable.Icon?,
+ val launcherIcon: Icon,
val useGrayColorFilter: Boolean,
val artistName: CharSequence,
val titleName: CharSequence,
val isExplicitVisible: Boolean,
+ val shouldAddGradient: Boolean,
val colorScheme: ColorScheme,
- val isTimeVisible: Boolean,
+ val canShowTime: Boolean,
val playTurbulenceNoise: Boolean,
val useSemanticActions: Boolean,
val actionButtons: List<MediaActionViewModel?>,
@@ -38,4 +40,6 @@
val gutsMenu: GutsViewModel,
val onClicked: (Expandable) -> Unit,
val onLongClicked: () -> Unit,
+ val onSeek: () -> Unit,
+ val onBindSeekbar: (SeekBarViewModel) -> Unit,
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
index b0375f0..a2307d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaRecommendationsViewModel.kt
@@ -213,9 +213,9 @@
return GutsViewModel(
gutsText =
applicationContext.getString(R.string.controls_media_close_session, model.appName),
- textColor = textPrimaryFromScheme(scheme),
- buttonBackgroundColor = accentPrimaryFromScheme(scheme),
- buttonTextColor = surfaceFromScheme(scheme),
+ textPrimaryColor = textPrimaryFromScheme(scheme),
+ accentPrimaryColor = accentPrimaryFromScheme(scheme),
+ surfaceColor = surfaceFromScheme(scheme),
onDismissClicked = {
onMediaRecommendationsDismissed(
model.key,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 452cb7e..ff8e903b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,8 +31,9 @@
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String?): LocalMediaManager {
- return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
- LocalMediaManager(context, localBluetoothManager, this, packageName)
- }
+ // TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user
+ // handle is necessary to disambiguate the same package running on different users.
+ return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+ .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4bd6da..4e77d13 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -21,16 +21,11 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import javax.inject.Inject
@SysUISingleton
-class MediaFlags
-@Inject
-constructor(
- private val featureFlags: FeatureFlagsClassic,
- private val sceneContainerFlags: SceneContainerFlags
-) {
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClassic) {
/**
* Check whether media control actions should be based on PlaybackState instead of notification
*/
@@ -57,7 +52,7 @@
/** Check whether to use scene framework */
fun isSceneContainerEnabled() =
- sceneContainerFlags.isEnabled() && MediaInSceneContainerFlag.isEnabled
+ SceneContainerFlag.isEnabled && MediaInSceneContainerFlag.isEnabled
/** Check whether to use media refactor code */
fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 54d175c..06267e2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -38,7 +38,9 @@
// Dismiss the previous dialog, if any.
mediaOutputBroadcastDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName)
+ // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
+ // disambiguate the same package running on different users.
+ val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 4db89d1..d6ca320 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -124,6 +124,7 @@
private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer";
private final String mPackageName;
+ private final UserHandle mUserHandle;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
private final LocalBluetoothManager mLocalBluetoothManager;
@@ -177,6 +178,7 @@
public MediaOutputController(
Context context,
@Assisted String packageName,
+ @Assisted @Nullable UserHandle userHandle,
MediaSessionManager mediaSessionManager,
@Nullable LocalBluetoothManager lbm,
ActivityStarter starter,
@@ -190,6 +192,7 @@
UserTracker userTracker) {
mContext = context;
mPackageName = packageName;
+ mUserHandle = userHandle;
mMediaSessionManager = mediaSessionManager;
mLocalBluetoothManager = lbm;
mActivityStarter = starter;
@@ -199,7 +202,8 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
+ InfoMediaManager imm =
+ InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -231,7 +235,7 @@
@AssistedFactory
public interface Factory {
/** Construct a MediaOutputController */
- MediaOutputController create(String packageName);
+ MediaOutputController create(String packageName, UserHandle userHandle);
}
protected void start(@NonNull Callback cb) {
@@ -946,11 +950,22 @@
}
void launchMediaOutputBroadcastDialog(View mediaOutputDialog, BroadcastSender broadcastSender) {
- MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
- mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
- mNotifCollection, mDialogTransitionAnimator, mNearbyMediaDevicesManager,
- mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
- mUserTracker);
+ MediaOutputController controller =
+ new MediaOutputController(
+ mContext,
+ mPackageName,
+ mUserHandle,
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mActivityStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyGuardManager,
+ mFeatureFlags,
+ mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
dialog.show();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index e7816a4..04d1492 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.dialog
import android.content.Context
+import android.os.UserHandle
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
@@ -41,7 +42,15 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
+ open fun createAndShow(
+ packageName: String,
+ aboveStatusBar: Boolean,
+ view: View? = null,
+ userHandle: UserHandle? = null
+ ) {
createAndShowWithController(
packageName,
aboveStatusBar,
@@ -55,20 +64,26 @@
)
)
},
+ userHandle = userHandle,
)
}
/** Creates a [MediaOutputDialog] for the given package. */
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
open fun createAndShowWithController(
packageName: String,
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
+ userHandle: UserHandle? = null,
) {
createAndShow(
packageName,
aboveStatusBar,
dialogTransitionAnimatorController = controller,
- includePlaybackAndAppMetadata = true
+ includePlaybackAndAppMetadata = true,
+ userHandle = userHandle,
)
}
@@ -79,20 +94,25 @@
packageName = null,
aboveStatusBar = false,
dialogTransitionAnimatorController = null,
- includePlaybackAndAppMetadata = false
+ includePlaybackAndAppMetadata = false,
+ userHandle = null,
)
}
+ // TODO: b/321969740 - Make the userHandle non-optional, and place the parameter next to the
+ // package name. The user handle is necessary to disambiguate the same package running on
+ // different users.
private fun createAndShow(
packageName: String?,
aboveStatusBar: Boolean,
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
- includePlaybackAndAppMetadata: Boolean = true
+ includePlaybackAndAppMetadata: Boolean = true,
+ userHandle: UserHandle? = null,
) {
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = mediaOutputControllerFactory.create(packageName)
+ val controller = mediaOutputControllerFactory.create(packageName, userHandle)
val mediaOutputDialog =
MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index da85234..9cc2888 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -55,8 +55,8 @@
@MainThread
public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
if (!TextUtils.isEmpty(packageName)) {
- // TODO: b/279555229 - Pass the userHandle into the output dialog manager.
- mMediaOutputDialogManager.createAndShow(packageName, false, null);
+ mMediaOutputDialogManager.createAndShow(
+ packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
index 9514c4a..b7942f7 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RemoteRecentSplitTaskTransitionRunner.kt
@@ -27,8 +27,8 @@
import android.util.Log
import android.view.SurfaceControl
import android.view.animation.DecelerateInterpolator
-import android.window.IRemoteTransition
import android.window.IRemoteTransitionFinishedCallback
+import android.window.RemoteTransitionStub
import android.window.TransitionInfo
import android.window.WindowContainerToken
import com.android.app.viewcapture.ViewCapture
@@ -41,7 +41,7 @@
private val viewPosition: IntArray,
private val screenBounds: Rect,
private val handleResult: () -> Unit,
-) : IRemoteTransition.Stub() {
+) : RemoteTransitionStub() {
override fun startAnimation(
transition: IBinder?,
info: TransitionInfo?,
@@ -114,14 +114,6 @@
}
}
- override fun mergeAnimation(
- transition: IBinder?,
- info: TransitionInfo?,
- t: SurfaceControl.Transaction?,
- mergeTarget: IBinder?,
- finishedCallback: IRemoteTransitionFinishedCallback?
- ) {}
-
@Throws(RemoteException::class)
override fun onTransitionConsumed(transition: IBinder, aborted: Boolean) {
Log.w(TAG, "unexpected consumption of app selector transition: aborted=$aborted")
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 63989ef..a6b6d61 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -35,6 +35,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.res.Configuration;
import android.database.ContentObserver;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
@@ -74,6 +75,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
@@ -101,7 +103,7 @@
AccessibilityButtonModeObserver.ModeChangedListener,
AccessibilityButtonTargetsObserver.TargetsChangedListener,
OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener,
- Dumpable, CommandQueue.Callbacks {
+ Dumpable, CommandQueue.Callbacks, ConfigurationController.ConfigurationListener {
private static final String TAG = NavBarHelper.class.getSimpleName();
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -189,6 +191,7 @@
UserTracker userTracker,
DisplayTracker displayTracker,
NotificationShadeWindowController notificationShadeWindowController,
+ ConfigurationController configurationController,
DumpManager dumpManager,
CommandQueue commandQueue,
@Main Executor mainExecutor) {
@@ -215,6 +218,7 @@
mNavBarMode = navigationModeController.addListener(this);
mCommandQueue.addCallback(this);
+ configurationController.addCallback(this);
overviewProxyService.addCallback(this);
dumpManager.registerDumpable(this);
}
@@ -359,6 +363,11 @@
updateA11yState();
}
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
+ }
+
/**
* Updates the current accessibility button state. The accessibility button state is only
* used for {@link Secure#ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR} and
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index ade56c4..b609864 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -902,11 +902,6 @@
refreshLayout(ld);
}
repositionNavigationBar(rotation);
- // NOTE(b/260220098): In some cases, the recreated nav bar will already have the right
- // configuration, which means that NavBarView will not receive a configuration change to
- // propagate to EdgeBackGestureHandler (which is injected into this and NBV). As such, we
- // should also force-update the gesture handler to ensure it updates to the right bounds
- mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
if (canShowSecondaryHandle()) {
if (rotation != mCurrentRotation) {
mCurrentRotation = rotation;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 9f7d1b3..12f2703 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -162,14 +162,11 @@
mIsLargeScreen = isLargeScreen(mContext);
boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
boolean largeScreenChanged = mIsLargeScreen != isOldConfigLargeScreen;
- // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+ " willApplyConfigToNavbars=" + willApplyConfig
+ " navBarCount=" + mNavigationBars.size());
- if (mTaskbarDelegate.isInitialized()) {
- mTaskbarDelegate.onConfigurationChanged(newConfig);
- }
// If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
if (largeScreenChanged && updateNavbarForTaskbar()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 1927f49..3c69ed9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -1031,7 +1031,6 @@
updateIcons(mTmpLastConfiguration);
updateRecentsIcon();
updateCurrentRotation();
- mEdgeBackGestureHandler.onConfigurationChanged(mConfiguration);
if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
|| mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
// If car mode or density changes, we need to reset the icons.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 5dd1bd8..f67973b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -39,7 +39,6 @@
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
@@ -72,7 +71,6 @@
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -250,8 +248,6 @@
mLightBarController.setNavigationBar(mLightBarTransitionsController);
mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
mEdgeBackGestureHandler.setBackAnimation(mBackAnimation);
- mEdgeBackGestureHandler.onConfigurationChanged(
- mContext.getResources().getConfiguration());
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
mInitialized = true;
} finally {
@@ -495,10 +491,6 @@
return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
}
- public void onConfigurationChanged(Configuration configuration) {
- mEdgeBackGestureHandler.onConfigurationChanged(configuration);
- }
-
@Override
public void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable) {
mOverviewProxyService.onNavigationBarLumaSamplingEnabled(displayId, enable);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 9d0ea5e..db4a7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1187,7 +1187,7 @@
updateDisabledForQuickstep(newConfig);
}
- // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+ // TODO(b/332635834): Disable this logging once b/332635834 is fixed.
Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ " lastReportedConfig=" + mLastReportedConfig);
mLastReportedConfig.updateFrom(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index ffbc560..7ccdf0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -24,7 +24,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
@@ -66,15 +66,14 @@
QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController,
ConfigurationController configurationController,
- FalsingManager falsingManager,
- SceneContainerFlags sceneContainerFlags) {
+ FalsingManager falsingManager) {
super(view);
mQsPanelController = qsPanelController;
mQuickStatusBarHeaderController = quickStatusBarHeaderController;
mConfigurationController = configurationController;
mFalsingManager = falsingManager;
mQSPanelContainer = mView.getQSPanelContainer();
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index a0607e9..1f4838e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -60,7 +60,7 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.MirrorController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
@@ -174,8 +174,6 @@
@Nullable
private View mFooterActionsView;
- private final SceneContainerFlags mSceneContainerFlags;
-
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue,
@@ -188,8 +186,7 @@
FooterActionsViewModel.Factory footerActionsViewModelFactory,
FooterActionsViewBinder footerActionsViewBinder,
LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags,
- SceneContainerFlags sceneContainerFlags) {
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
@@ -205,8 +202,7 @@
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
- mSceneContainerFlags = sceneContainerFlags;
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mStatusBarState = StatusBarState.SHADE;
}
}
@@ -224,7 +220,7 @@
mQSPanelController.init();
mQuickQSPanelController.init();
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mQSFooterActionsViewModel = mFooterActionsViewModelFactory
.create(mListeningAndVisibilityLifecycleOwner);
bindFooterActionsView(mRootView);
@@ -249,7 +245,7 @@
mScrollListener.onQsPanelScrollChanged(scrollY);
}
});
- mQSPanelScrollView.setScrollingEnabled(!mSceneContainerFlags.isEnabled());
+ mQSPanelScrollView.setScrollingEnabled(!SceneContainerFlag.isEnabled());
mHeader = mRootView.findViewById(R.id.header);
mFooter = qsComponent.getQSFooter();
@@ -509,7 +505,7 @@
@VisibleForTesting
boolean isKeyguardState() {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return false;
} else {
// We want the freshest state here since otherwise we'll have some weirdness if earlier
@@ -573,7 +569,7 @@
}
private void setKeyguardShowing(boolean keyguardShowing) {
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mLastQSExpansion = -1;
@@ -651,7 +647,7 @@
@Override
public int getHeightDiff() {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return mQSPanelController.getViewBottom() - mHeader.getBottom()
+ mHeader.getPaddingBottom();
} else {
@@ -720,7 +716,7 @@
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
float qsScrollViewTranslation =
onKeyguard && !mShowCollapsedOnKeyguard ? panelTranslationY : 0;
mQSPanelScrollView.setTranslationY(qsScrollViewTranslation);
@@ -824,7 +820,7 @@
mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
mQSPanelScrollView.getHeight());
}
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
mQSPanelScrollView.setClipBounds(mQsBounds);
mQSPanelScrollView.getLocationOnScreen(mLocationTemp);
@@ -907,7 +903,7 @@
// The customize state changed, so our height changed.
mContainer.updateExpansion();
boolean customizing = isCustomizing();
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mQSPanelController.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
} else {
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
@@ -984,7 +980,7 @@
@Override
public void onStateChanged(int newState) {
- if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) {
+ if (SceneContainerFlag.isEnabled() || newState == mStatusBarState) {
return;
}
mStatusBarState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index b8c3c1a..e24caf1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -37,7 +37,7 @@
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -94,7 +94,6 @@
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
SplitShadeStateController splitShadeStateController,
- SceneContainerFlags sceneContainerFlags,
Provider<QSLongPressEffect> longPRessEffectProvider) {
super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
@@ -113,7 +112,7 @@
mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController);
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLastDensity = view.getResources().getConfiguration().densityDpi;
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 1d92d78..bb40d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,7 +17,7 @@
package com.android.systemui.qs;
import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -34,12 +34,11 @@
@Inject
QuickStatusBarHeaderController(QuickStatusBarHeader view,
- QuickQSPanelController quickQSPanelController,
- SceneContainerFlags sceneContainerFlags
+ QuickQSPanelController quickQSPanelController
) {
super(view);
mQuickQSPanelController = quickQSPanelController;
- mSceneContainerEnabled = sceneContainerFlags.isEnabled();
+ mSceneContainerEnabled = SceneContainerFlag.isEnabled();
}
@Override
protected void onViewAttached() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index a01d658..10c8e53 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 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,124 +16,21 @@
package com.android.systemui.qs;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerExecutor;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.util.settings.SecureSettings;
-import java.util.ArrayList;
-
-import javax.inject.Inject;
-
-/**
- * @hide
- */
-@SysUISingleton
-public class ReduceBrightColorsController implements
+public interface ReduceBrightColorsController extends
CallbackController<ReduceBrightColorsController.Listener> {
- private final ColorDisplayManager mManager;
- private final UserTracker mUserTracker;
- private UserTracker.Callback mCurrentUserTrackerCallback;
- private final Handler mHandler;
- private final ContentObserver mContentObserver;
- private final SecureSettings mSecureSettings;
- private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
-
- @Inject
- public ReduceBrightColorsController(UserTracker userTracker,
- @Background Handler handler,
- ColorDisplayManager colorDisplayManager,
- SecureSettings secureSettings) {
- mManager = colorDisplayManager;
- mUserTracker = userTracker;
- mHandler = handler;
- mSecureSettings = secureSettings;
- mContentObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- final String setting = uri == null ? null : uri.getLastPathSegment();
- synchronized (mListeners) {
- if (setting != null && mListeners.size() != 0) {
- if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
- dispatchOnActivated(mManager.isReduceBrightColorsActivated());
- }
- }
- }
- }
- };
-
- mCurrentUserTrackerCallback = new UserTracker.Callback() {
- @Override
- public void onUserChanged(int newUser, Context userContext) {
- synchronized (mListeners) {
- if (mListeners.size() > 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, newUser);
- }
- }
- }
- };
- mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
- }
-
- @Override
- public void addCallback(@NonNull Listener listener) {
- synchronized (mListeners) {
- if (!mListeners.contains(listener)) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- mSecureSettings.registerContentObserverForUser(
- Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- false, mContentObserver, mUserTracker.getUserId());
- }
- }
- }
- }
-
- @Override
- public void removeCallback(@androidx.annotation.NonNull Listener listener) {
- synchronized (mListeners) {
- if (mListeners.remove(listener) && mListeners.size() == 0) {
- mSecureSettings.unregisterContentObserver(mContentObserver);
- }
- }
- }
/** Returns {@code true} if Reduce Bright Colors is activated */
- public boolean isReduceBrightColorsActivated() {
- return mManager.isReduceBrightColorsActivated();
- }
+ boolean isReduceBrightColorsActivated();
/** Sets the activation state of Reduce Bright Colors */
- public void setReduceBrightColorsActivated(boolean activated) {
- mManager.setReduceBrightColorsActivated(activated);
- }
-
- private void dispatchOnActivated(boolean activated) {
- ArrayList<Listener> copy = new ArrayList<>(mListeners);
- for (Listener l : copy) {
- l.onActivated(activated);
- }
- }
+ void setReduceBrightColorsActivated(boolean activated);
/**
* Listener invoked whenever the Reduce Bright Colors settings are changed.
*/
- public interface Listener {
+ interface Listener {
/**
* Listener invoked when the activated state changes.
*
@@ -142,4 +39,4 @@
default void onActivated(boolean activated) {
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
new file mode 100644
index 0000000..4fc6609
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+/**
+ * @hide
+ */
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+@SysUISingleton
+public class ReduceBrightColorsControllerImpl implements
+ ReduceBrightColorsController {
+ private final ColorDisplayManager mManager;
+ private final UserTracker mUserTracker;
+ private UserTracker.Callback mCurrentUserTrackerCallback;
+ private final Handler mHandler;
+ private final ContentObserver mContentObserver;
+ private final SecureSettings mSecureSettings;
+ private final ArrayList<ReduceBrightColorsController.Listener> mListeners = new ArrayList<>();
+
+ @Inject
+ public ReduceBrightColorsControllerImpl(UserTracker userTracker,
+ @Background Handler handler,
+ ColorDisplayManager colorDisplayManager,
+ SecureSettings secureSettings) {
+ mManager = colorDisplayManager;
+ mUserTracker = userTracker;
+ mHandler = handler;
+ mSecureSettings = secureSettings;
+ mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ final String setting = uri == null ? null : uri.getLastPathSegment();
+ synchronized (mListeners) {
+ if (setting != null && mListeners.size() != 0) {
+ if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
+ dispatchOnActivated(mManager.isReduceBrightColorsActivated());
+ }
+ }
+ }
+ }
+ };
+
+ mCurrentUserTrackerCallback = new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, Context userContext) {
+ synchronized (mListeners) {
+ if (mListeners.size() > 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, newUser);
+ }
+ }
+ }
+ };
+ mUserTracker.addCallback(mCurrentUserTrackerCallback, new HandlerExecutor(handler));
+ }
+
+ @Override
+ public void addCallback(@NonNull Listener listener) {
+ synchronized (mListeners) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
+ false, mContentObserver, mUserTracker.getUserId());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void removeCallback(@androidx.annotation.NonNull Listener listener) {
+ synchronized (mListeners) {
+ if (mListeners.remove(listener) && mListeners.size() == 0) {
+ mSecureSettings.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ @Override
+ public boolean isReduceBrightColorsActivated() {
+ return mManager.isReduceBrightColorsActivated();
+ }
+
+ @Override
+ public void setReduceBrightColorsActivated(boolean activated) {
+ mManager.setReduceBrightColorsActivated(activated);
+ }
+
+ private void dispatchOnActivated(boolean activated) {
+ ArrayList<Listener> copy = new ArrayList<>(mListeners);
+ for (Listener l : copy) {
+ l.onActivated(activated);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
index 34b1b2d..a222b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -42,7 +42,7 @@
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -107,8 +107,7 @@
protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
QSHost qsHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
KeyguardStateController keyguardStateController, LightBarController lightBarController,
- ConfigurationController configurationController, UiEventLogger uiEventLogger,
- SceneContainerFlags sceneContainerFlags) {
+ ConfigurationController configurationController, UiEventLogger uiEventLogger) {
super(view);
mTileQueryHelper = tileQueryHelper;
mQsHost = qsHost;
@@ -118,7 +117,7 @@
mLightBarController = lightBarController;
mConfigurationController = configurationController;
mUiEventLogger = uiEventLogger;
- view.setSceneContainerEnabled(sceneContainerFlags.isEnabled());
+ view.setSceneContainerEnabled(SceneContainerFlag.isEnabled());
mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 8d3500a..b705a03 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -28,6 +28,7 @@
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
import com.android.systemui.qs.external.QSExternalModule;
import com.android.systemui.qs.panels.dagger.PanelsModule;
import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
@@ -118,4 +119,11 @@
@Binds
QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
+
+ /**
+ * Dims the screen
+ */
+ @Binds
+ ReduceBrightColorsController bindReduceBrightColorsController(
+ ReduceBrightColorsControllerImpl impl);
}
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 6c9a8a4..5122e1f 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
@@ -28,6 +28,7 @@
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
+import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel
import javax.inject.Inject
import javax.inject.Provider
@@ -57,7 +58,9 @@
val viewModel: QSTileViewModel =
when (val spec = TileSpec.create(tileSpec)) {
is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
- is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
+ // when using the stub, we default to old tile rather than adding the stub
+ is TileSpec.PlatformTileSpec ->
+ tileMap[tileSpec]?.get()?.takeIf { it !is StubQSTileViewModel }
is TileSpec.Invalid -> null
}
?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
new file mode 100644
index 0000000..98fd561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileDataInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.tiles.impl.reducebrightness.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.util.kotlin.isEnabled
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Observes reduce bright colors state changes providing the [ReduceBrightColorsTileModel]. */
+class ReduceBrightColorsTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ @Named(RBC_AVAILABLE) private val isAvailable: Boolean,
+ private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileDataInteractor<ReduceBrightColorsTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<ReduceBrightColorsTileModel> {
+ return reduceBrightColorsController
+ .isEnabled()
+ .distinctUntilChanged()
+ .map { ReduceBrightColorsTileModel(it) }
+ .flowOn(bgCoroutineContext)
+ }
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(isAvailable)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
new file mode 100644
index 0000000..762f863
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.ReduceBrightColorsController
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles reduce bright colors tile clicks. */
+class ReduceBrightColorsTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val reduceBrightColorsController: ReduceBrightColorsController,
+) : QSTileUserActionInteractor<ReduceBrightColorsTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<ReduceBrightColorsTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ reduceBrightColorsController.setReduceBrightColorsActivated(
+ !input.data.isEnabled
+ )
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
similarity index 63%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.kt
index 979d8e7..05e0f5d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/model/ReduceBrightColorsTileModel.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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.reducebrightness.domain.model
-import com.android.systemui.kosmos.Kosmos
-
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+/**
+ * Reduce bright colors tile model.
+ *
+ * @param isEnabled is true when the reduce bright colors is enabled;
+ */
+@JvmInline value class ReduceBrightColorsTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
new file mode 100644
index 0000000..fca93df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.tiles.impl.reducebrightness.ui
+
+import android.content.res.Resources
+import android.service.quicksettings.Tile
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
+class ReduceBrightColorsTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+
+ override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ if (data.isEnabled) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ icon = {
+ Icon.Loaded(
+ drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_on, theme),
+ contentDescription = null
+ )
+ }
+
+ secondaryLabel =
+ resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_ACTIVE]
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ icon = {
+ Icon.Loaded(
+ drawable = resources.getDrawable(R.drawable.qs_extra_dim_icon_off, theme),
+ contentDescription = null
+ )
+ }
+ secondaryLabel =
+ resources
+ .getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
+ }
+ label =
+ resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
+ contentDescription = label
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
new file mode 100644
index 0000000..64f1fd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/StubQSTileViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.tiles.viewmodel
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+
+object StubQSTileViewModel : QSTileViewModel {
+
+ override val state: SharedFlow<QSTileState>
+ get() = error("Don't call stubs")
+
+ override val config: QSTileConfig
+ get() = error("Don't call stubs")
+
+ override val isAvailable: StateFlow<Boolean>
+ get() = error("Don't call stubs")
+
+ override fun onUserChanged(user: UserHandle) = error("Don't call stubs")
+
+ override fun forceUpdate() = error("Don't call stubs")
+
+ override fun onActionPerformed(userAction: QSTileUserAction) = error("Don't call stubs")
+
+ override fun destroy() = error("Don't call stubs")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4ece7b6..1ddc094 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -98,7 +98,7 @@
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -146,7 +146,6 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
- private final SceneContainerFlags mSceneContainerFlags;
private final Executor mMainExecutor;
private final ShellInterface mShellInterface;
private final Lazy<ShadeViewController> mShadeViewControllerLazy;
@@ -208,7 +207,7 @@
@Override
public void onStatusBarTouchEvent(MotionEvent event) {
verifyCallerAndClearCallingIdentity("onStatusBarTouchEvent", () -> {
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
//TODO(b/329863123) implement latency tracking for shade scene
Log.i(TAG_OPS, "Scene container enabled. Latency tracking not started.");
} else if (event.getActionMasked() == ACTION_DOWN) {
@@ -223,7 +222,7 @@
// If scene framework is enabled, set the scene container window to
// visible and let the touch "slip" into that window.
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
} else {
mShadeViewControllerLazy.get().startInputFocusTransfer();
@@ -232,7 +231,7 @@
if (action == ACTION_UP || action == ACTION_CANCEL) {
mInputFocusTransferStarted = false;
- if (!mSceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled()) {
float velocity = (event.getY() - mInputFocusTransferStartY)
/ (event.getEventTime() - mInputFocusTransferStartMillis);
if (action == ACTION_CANCEL) {
@@ -612,7 +611,6 @@
KeyguardUnlockAnimationController sysuiUnlockAnimationController,
InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager,
AssistUtils assistUtils,
- SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
BroadcastDispatcher broadcastDispatcher
@@ -624,7 +622,6 @@
}
mContext = context;
- mSceneContainerFlags = sceneContainerFlags;
mMainExecutor = mainExecutor;
mShellInterface = shellInterface;
mShadeViewControllerLazy = shadeViewControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index afd0746..8277c73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Module
@@ -29,7 +28,6 @@
EmptySceneModule::class,
GoneSceneModule::class,
QuickSettingsSceneModule::class,
- SceneContainerFlagsModule::class,
ShadeSceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 62b0914..69f9443 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -20,7 +20,6 @@
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Binds
@@ -40,7 +39,6 @@
GoneSceneModule::class,
LockscreenSceneModule::class,
QuickSettingsSceneModule::class,
- SceneContainerFlagsModule::class,
ShadeSceneModule::class,
],
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index 0665c9e..d202c24 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -16,7 +16,6 @@
package com.android.systemui.scene
-import com.android.systemui.scene.shared.flag.SceneContainerFlagsModule
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import dagger.Module
@@ -30,7 +29,6 @@
EmptySceneModule::class,
GoneSceneModule::class,
LockscreenSceneModule::class,
- SceneContainerFlagsModule::class,
],
)
object ShadelessSceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index c736707..1cf1c18 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,7 @@
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -53,7 +53,6 @@
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
- sceneContainerFlags: SceneContainerFlags,
sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
@@ -68,7 +67,7 @@
* false if the bouncer is visible.
*/
val isLockscreenOrShadeVisible: StateFlow<Boolean> =
- if (!sceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
} else {
sceneInteractorProvider
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 0e66c28..4774eb3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -44,7 +44,7 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -92,7 +92,6 @@
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val bouncerInteractor: BouncerInteractor,
private val keyguardInteractor: KeyguardInteractor,
- private val flags: SceneContainerFlags,
private val sysUiState: SysUiState,
@DisplayId private val displayId: Int,
private val sceneLogger: SceneLogger,
@@ -111,7 +110,7 @@
) : CoreStartable {
override fun start() {
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
sceneLogger.logFrameworkEnabled(isEnabled = true)
hydrateVisibility()
automaticallySwitchScenes()
@@ -124,16 +123,18 @@
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
- reason = flags.requirementDescription(),
+ reason = SceneContainerFlag.requirementDescription(),
)
}
}
override fun dump(pw: PrintWriter, args: Array<out String>) =
pw.asIndenting().run {
- printSection("SceneContainerFlags") {
- println("isEnabled", flags.isEnabled())
- printSection("requirementDescription") { println(flags.requirementDescription()) }
+ printSection("SceneContainerFlag") {
+ println("isEnabled", SceneContainerFlag.isEnabled)
+ printSection("requirementDescription") {
+ println(SceneContainerFlag.requirementDescription())
+ }
}
}
@@ -288,7 +289,8 @@
Scenes.Gone to "device was unlocked in Bouncer scene"
} else {
val prevScene = previousScene.value
- (prevScene ?: Scenes.Gone) to
+ (prevScene
+ ?: Scenes.Gone) to
"device was unlocked in Bouncer scene, from sceneKey=$prevScene"
}
isOnLockscreen ->
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
similarity index 84%
rename from packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
rename to packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
index cff11a7..234eda8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt
@@ -20,7 +20,6 @@
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.Flags.sceneContainer
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
@@ -32,8 +31,6 @@
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag
-import dagger.Module
-import dagger.Provides
/** Helper for reading or using the scene container flag state. */
object SceneContainerFlag {
@@ -99,30 +96,12 @@
*/
@JvmStatic
inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, DESCRIPTION)
-}
-
-/**
- * Defines interface for classes that can check whether the scene container framework feature is
- * enabled.
- */
-interface SceneContainerFlags {
-
- /** Returns `true` if the Scene Container Framework is enabled; `false` otherwise. */
- fun isEnabled(): Boolean
/** Returns a developer-readable string that describes the current requirement list. */
- fun requirementDescription(): String
-}
-
-class SceneContainerFlagsImpl : SceneContainerFlags {
-
- override fun isEnabled(): Boolean {
- return SceneContainerFlag.isEnabled
- }
-
- override fun requirementDescription(): String {
+ @JvmStatic
+ fun requirementDescription(): String {
return buildString {
- SceneContainerFlag.getAllRequirements().forEach { requirement ->
+ getAllRequirements().forEach { requirement ->
append('\n')
append(if (requirement.isEnabled) " [MET]" else "[NOT MET]")
append(" ${requirement.name}")
@@ -130,9 +109,3 @@
}
}
}
-
-@Module
-object SceneContainerFlagsModule {
-
- @Provides @SysUISingleton fun impl(): SceneContainerFlags = SceneContainerFlagsImpl()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index 67dc0cc..259a8bf 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -4,7 +4,6 @@
import android.util.AttributeSet
import android.view.View
import android.view.WindowInsets
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -31,7 +30,6 @@
viewModel: SceneContainerViewModel,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
@@ -44,7 +42,6 @@
windowInsets = windowInsets,
containerConfig = containerConfig,
sharedNotificationContainer = sharedNotificationContainer,
- flags = flags,
scenes = scenes,
onVisibilityChangedInternal = { isVisible ->
super.setVisibility(if (isVisible) View.VISIBLE else View.INVISIBLE)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 809ac2e..2ef9b73 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -39,7 +39,7 @@
import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -63,7 +63,6 @@
windowInsets: StateFlow<WindowInsets?>,
containerConfig: SceneContainerConfig,
sharedNotificationContainer: SharedNotificationContainer,
- flags: SceneContainerFlags,
scenes: Set<Scene>,
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
@@ -115,7 +114,7 @@
// the SceneContainerView. This SharedNotificationContainer should contain NSSL
// due to the NotificationStackScrollLayoutSection (legacy) or
// NotificationSection (scene container) moving it there.
- if (flags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
(sharedNotificationContainer.parent as? ViewGroup)?.removeView(
sharedNotificationContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 706ac9c..b43a1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
@@ -38,9 +39,11 @@
import android.view.Display;
import android.view.ScrollCaptureResponse;
import android.view.View;
+import android.view.WindowInsets;
import android.widget.ImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.view.WindowCompat;
import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
@@ -127,6 +130,10 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // Enable edge-to-edge explicitly.
+ WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
+
setContentView(R.layout.long_screenshot);
mPreview = requireViewById(R.id.preview);
@@ -149,6 +156,13 @@
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateImageDimensions());
+ requireViewById(R.id.root).setOnApplyWindowInsetsListener(
+ (view, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars());
+ view.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsets.CONSUMED;
+ });
+
Intent intent = getIntent();
mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE);
mScreenshotUserHandle = intent.getParcelableExtra(EXTRA_SCREENSHOT_USER_HANDLE,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d9a5102..5f835b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -95,7 +95,7 @@
// mean that the new action must be inserted here.
val actionButton =
layoutInflater.inflate(
- R.layout.overlay_action_chip,
+ R.layout.shelf_action_chip,
actionsContainer,
false
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index fb32b9f..adcb14a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -59,7 +59,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -117,7 +117,6 @@
private final AuthController mAuthController;
private final Lazy<SelectedUserInteractor> mUserInteractor;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
- private final SceneContainerFlags mSceneContainerFlags;
private final Lazy<CommunalInteractor> mCommunalInteractor;
private ViewGroup mWindowRootView;
private LayoutParams mLp;
@@ -166,7 +165,6 @@
ShadeWindowLogger logger,
Lazy<SelectedUserInteractor> userInteractor,
UserTracker userTracker,
- SceneContainerFlags sceneContainerFlags,
Lazy<CommunalInteractor> communalInteractor) {
mContext = context;
mWindowRootViewComponentFactory = windowRootViewComponentFactory;
@@ -186,7 +184,6 @@
dumpManager.registerCriticalDumpable("{slow}NotificationShadeWindowControllerImpl", this);
mAuthController = authController;
mUserInteractor = userInteractor;
- mSceneContainerFlags = sceneContainerFlags;
mCommunalInteractor = communalInteractor;
mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed();
mLockScreenDisplayTimeout = context.getResources()
@@ -289,7 +286,7 @@
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
// This prevents the appearance and disappearance of the software keyboard (also known
// as the "IME") from scrolling/panning the window to make room for the keyboard.
//
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 648d4b5..a0c9391 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -19,7 +19,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.qs.QSContainerController
import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -50,11 +50,10 @@
@Provides
@SysUISingleton
fun provideBaseShadeInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
): BaseShadeInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -64,11 +63,10 @@
@Provides
@SysUISingleton
fun provideShadeController(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeControllerSceneImpl>,
sceneContainerOff: Provider<ShadeControllerImpl>
): ShadeController {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -78,11 +76,10 @@
@Provides
@SysUISingleton
fun provideShadeAnimationInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeAnimationInteractorLegacyImpl>
): ShadeAnimationInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -92,11 +89,10 @@
@Provides
@SysUISingleton
fun provideShadeBackActionInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeBackActionInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): ShadeBackActionInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -106,11 +102,10 @@
@Provides
@SysUISingleton
fun provideShadeLockscreenInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeLockscreenInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): ShadeLockscreenInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -120,11 +115,10 @@
@Provides
@SysUISingleton
fun providePanelExpansionInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<PanelExpansionInteractorImpl>,
sceneContainerOff: Provider<NotificationPanelViewController>
): PanelExpansionInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
@@ -134,11 +128,10 @@
@Provides
@SysUISingleton
fun provideQuickSettingsController(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
sceneContainerOff: Provider<QuickSettingsControllerImpl>,
): QuickSettingsController {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index f5dd5e4..bc23778 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -33,7 +33,7 @@
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scene
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -78,15 +78,13 @@
@SysUISingleton
fun providesWindowRootView(
layoutInflater: LayoutInflater,
- sceneContainerFlags: SceneContainerFlags,
viewModelProvider: Provider<SceneContainerViewModel>,
containerConfigProvider: Provider<SceneContainerConfig>,
- flagsProvider: Provider<SceneContainerFlags>,
scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
layoutInsetController: NotificationInsetsController,
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
): WindowRootView {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
val sceneWindowRootView =
layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
@@ -95,7 +93,6 @@
containerConfig = containerConfigProvider.get(),
sharedNotificationContainer =
sceneWindowRootView.requireViewById(R.id.shared_notification_container),
- flags = flagsProvider.get(),
scenes = scenesProvider.get(),
layoutInsetController = layoutInsetController,
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
@@ -115,9 +112,8 @@
@SysUISingleton
fun providesNotificationShadeWindowView(
root: WindowRootView,
- sceneContainerFlags: SceneContainerFlags,
): NotificationShadeWindowView {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
return root.requireViewById(R.id.legacy_window_root)
}
return root as NotificationShadeWindowView?
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 72a9c8d..6c76061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -22,9 +22,11 @@
import android.icu.text.DateFormat
import android.icu.text.DisplayContext
import android.os.UserHandle
+import android.provider.Settings
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
@@ -54,6 +56,7 @@
constructor(
@Application private val applicationScope: CoroutineScope,
context: Context,
+ private val activityStarter: ActivityStarter,
shadeInteractor: ShadeInteractor,
mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
@@ -136,6 +139,14 @@
clockInteractor.launchClockActivity()
}
+ /** Notifies that the shadeCarrierGroup was clicked. */
+ fun onShadeCarrierGroupClicked() {
+ activityStarter.postStartActivityDismissingKeyguard(
+ Intent(Settings.ACTION_WIRELESS_SETTINGS),
+ 0
+ )
+ }
+
private fun updateDateTexts(invalidateFormats: Boolean) {
if (invalidateFormats) {
longerDateFormat.value = getFormatFromPattern(longerPattern)
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 980f665a..6800c61 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
@@ -37,6 +37,7 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -64,6 +65,7 @@
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
private val sceneInteractor: SceneInteractor,
+ unfoldTransitionInteractor: UnfoldTransitionInteractor,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
combine(
@@ -106,6 +108,16 @@
val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
+ /**
+ * The unfold transition progress. When fully-unfolded, this is `1` and fully folded, it's `0`.
+ */
+ val unfoldTransitionProgress: StateFlow<Float> =
+ unfoldTransitionInteractor.unfoldProgress.stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 1f
+ )
+
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() {
if (!isClickable.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index e5b6497..594c191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -35,7 +35,7 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shade.ShadeSurface;
@@ -61,8 +61,6 @@
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import javax.inject.Provider;
-
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
@@ -70,6 +68,8 @@
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
+import javax.inject.Provider;
+
/**
* This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
* this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -185,10 +185,9 @@
@Provides
@SysUISingleton
static ShadeSurface provideShadeSurface(
- SceneContainerFlags sceneContainerFlags,
Provider<ShadeSurfaceImpl> sceneContainerOn,
Provider<NotificationPanelViewController> sceneContainerOff) {
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
return sceneContainerOn.get();
} else {
return sceneContainerOff.get();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 741102b..cf5366b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -29,7 +29,6 @@
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -49,7 +48,6 @@
class SharedNotificationContainerBinder
@Inject
constructor(
- private val sceneContainerFlags: SceneContainerFlags,
private val controller: NotificationStackScrollLayoutController,
private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
private val notificationScrollViewBinder: NotificationScrollViewBinder,
@@ -130,7 +128,7 @@
.collect { controller.setMaxDisplayedNotifications(it) }
}
- if (!sceneContainerFlags.isEnabled()) {
+ if (!SceneContainerFlag.isEnabled) {
launch {
viewModel.bounds.collect {
val animate =
@@ -166,7 +164,7 @@
}
}
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
disposables += notificationScrollViewBinder.bindWhileAttached()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index b284179..ca19da5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -22,7 +22,7 @@
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
@@ -43,7 +43,6 @@
dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
- flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
private val keyguardInteractor: KeyguardInteractor,
) : FlowDumperImpl(dumpManager) {
@@ -51,7 +50,7 @@
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
/** DEBUG: whether the debug logging should be output. */
- val isDebugLoggingEnabled: Boolean = flags.isEnabled()
+ val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled
/** Notifies that the bounds of the notification scrim have changed. */
fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1d6b744..cb3c03e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -164,7 +164,6 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -592,9 +591,6 @@
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
-
- private final SceneContainerFlags mSceneContainerFlags;
-
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor;
/**
@@ -708,7 +704,6 @@
UserTracker userTracker,
Provider<FingerprintManager> fingerprintManager,
ActivityStarter activityStarter,
- SceneContainerFlags sceneContainerFlags,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor
) {
mContext = context;
@@ -804,7 +799,6 @@
mUserTracker = userTracker;
mFingerprintManager = fingerprintManager;
mActivityStarter = activityStarter;
- mSceneContainerFlags = sceneContainerFlags;
mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -1088,7 +1082,7 @@
mJavaAdapter.alwaysCollectFlow(
mCommunalInteractor.isIdleOnCommunal(),
mIdleOnCommunalConsumer);
- if (mSceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
mJavaAdapter.alwaysCollectFlow(
mBrightnessMirrorShowingInteractor.isShowing(),
this::setBrightnessMirrorShowing
@@ -1277,7 +1271,7 @@
// Set up the quick settings tile panel
final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
- if (container != null && !mSceneContainerFlags.isEnabled()) {
+ if (container != null && !SceneContainerFlag.isEnabled()) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
ExtensionFragmentListener.attachExtensonToFragment(
@@ -1545,8 +1539,7 @@
mShadeSurface,
mShadeExpansionStateManager,
mBiometricUnlockController,
- mStackScroller,
- mKeyguardBypassController);
+ mStackScroller);
mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index a3d316b..a8941bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.phone
import android.annotation.IntDef
-import android.content.Context
import android.content.pm.PackageManager
+import android.content.res.Resources
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import androidx.annotation.VisibleForTesting
@@ -26,7 +26,10 @@
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.ShadeRepository
@@ -46,12 +49,20 @@
import javax.inject.Inject
@SysUISingleton
-open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassController {
+class KeyguardBypassController @Inject constructor(
+ @Main resources: Resources,
+ packageManager: PackageManager,
+ @Application applicationScope: CoroutineScope,
+ tunerService: TunerService,
+ private val statusBarStateController: StatusBarStateController,
+ lockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardStateController: KeyguardStateController,
+ private val shadeRepository: ShadeRepository,
+ devicePostureController: DevicePostureController,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ dumpManager: DumpManager
+) : Dumpable, StackScrollAlgorithm.BypassController {
- private val mKeyguardStateController: KeyguardStateController
- private val statusBarStateController: StatusBarStateController
- private val shadeRepository: ShadeRepository
- private val devicePostureController: DevicePostureController
@BypassOverride private val bypassOverride: Int
private var hasFaceFeature: Boolean
@DevicePostureInt private val configFaceAuthSupportedPosture: Int
@@ -99,7 +110,7 @@
FACE_UNLOCK_BYPASS_NEVER -> false
else -> field
}
- return enabled && mKeyguardStateController.isFaceEnrolledAndEnabled &&
+ return enabled && keyguardStateController.isFaceEnrolledAndEnabled &&
isPostureAllowedForFaceAuth()
}
private set(value) {
@@ -108,70 +119,44 @@
}
var bouncerShowing: Boolean = false
- var altBouncerShowing: Boolean = false
var launchingAffordance: Boolean = false
var qsExpanded = false
- @Inject
- constructor(
- context: Context,
- @Application applicationScope: CoroutineScope,
- tunerService: TunerService,
- statusBarStateController: StatusBarStateController,
- lockscreenUserManager: NotificationLockscreenUserManager,
- keyguardStateController: KeyguardStateController,
- shadeRepository: ShadeRepository,
- devicePostureController: DevicePostureController,
- dumpManager: DumpManager
- ) {
- this.mKeyguardStateController = keyguardStateController
- this.statusBarStateController = statusBarStateController
- this.shadeRepository = shadeRepository
- this.devicePostureController = devicePostureController
-
- bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+ init {
+ bypassOverride = resources.getInteger(R.integer.config_face_unlock_bypass_override)
configFaceAuthSupportedPosture =
- context.resources.getInteger(R.integer.config_face_auth_supported_posture)
-
- hasFaceFeature = context.packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
- if (!hasFaceFeature) {
- return
- }
-
-
- if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
- devicePostureController.addCallback { posture ->
- if (postureState != posture) {
- postureState = posture
- notifyListeners()
+ resources.getInteger(R.integer.config_face_auth_supported_posture)
+ hasFaceFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)
+ if (hasFaceFeature) {
+ if (configFaceAuthSupportedPosture != DEVICE_POSTURE_UNKNOWN) {
+ devicePostureController.addCallback { posture ->
+ if (postureState != posture) {
+ postureState = posture
+ notifyListeners()
+ }
}
}
- }
-
- dumpManager.registerNormalDumpable("KeyguardBypassController", this)
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onStateChanged(newState: Int) {
- if (newState != StatusBarState.KEYGUARD) {
- pendingUnlock = null
- }
- }
- })
-
- listenForQsExpandedChange(applicationScope)
-
- val dismissByDefault = if (context.resources.getBoolean(
- com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
-
- tunerService.addTunable({ key, _ ->
- bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
- }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-
- lockscreenUserManager.addUserChangedListener(
- object : NotificationLockscreenUserManager.UserChangedListener {
- override fun onUserChanged(userId: Int) {
- pendingUnlock = null
+ dumpManager.registerNormalDumpable("KeyguardBypassController", this)
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ if (newState != StatusBarState.KEYGUARD) {
+ pendingUnlock = null
+ }
}
})
+ listenForQsExpandedChange(applicationScope)
+ val dismissByDefault = if (resources.getBoolean(
+ com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0
+ tunerService.addTunable({ key, _ ->
+ bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
+ }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
+ lockscreenUserManager.addUserChangedListener(
+ object : NotificationLockscreenUserManager.UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ pendingUnlock = null
+ }
+ })
+ }
}
@VisibleForTesting
@@ -228,7 +213,8 @@
if (bypassEnabled) {
return when {
bouncerShowing -> true
- altBouncerShowing -> true
+ keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER ->
+ true
statusBarStateController.state != StatusBarState.KEYGUARD -> false
launchingAffordance -> false
isPulseExpanding || qsExpanded -> false
@@ -260,7 +246,8 @@
pw.println(" bypassEnabled: $bypassEnabled")
pw.println(" canBypass: ${canBypass()}")
pw.println(" bouncerShowing: $bouncerShowing")
- pw.println(" altBouncerShowing: $altBouncerShowing")
+ pw.println(" altBouncerShowing:" +
+ " ${keyguardTransitionInteractor.getCurrentState() == KeyguardState.ALTERNATE_BOUNCER}")
pw.println(" isPulseExpanding: $isPulseExpanding")
pw.println(" launchingAffordance: $launchingAffordance")
pw.println(" qSExpanded: $qsExpanded")
@@ -273,7 +260,7 @@
val start = listeners.isEmpty()
listeners.add(listener)
if (start) {
- mKeyguardStateController.addCallback(faceAuthEnabledChangedCallback)
+ keyguardStateController.addCallback(faceAuthEnabledChangedCallback)
}
}
@@ -284,7 +271,7 @@
fun unregisterOnBypassStateChangedListener(listener: OnBypassStateChangedListener) {
listeners.remove(listener)
if (listeners.isEmpty()) {
- mKeyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
+ keyguardStateController.removeCallback(faceAuthEnabledChangedCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 92fd90a..5206e46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -24,10 +24,10 @@
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.Gefingerpoken
-import com.android.systemui.res.R
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeLogger
@@ -65,7 +65,6 @@
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val sceneContainerFlags: SceneContainerFlags,
private val configurationController: ConfigurationController,
private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
) : ViewController<PhoneStatusBarView>(view) {
@@ -205,7 +204,7 @@
// If scene framework is enabled, route the touch to it and
// ignore the rest of the gesture.
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled) {
windowRootView.get().dispatchTouchEvent(event)
return true
}
@@ -267,7 +266,6 @@
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
private val featureFlags: FeatureFlags,
- private val sceneContainerFlags: SceneContainerFlags,
private val userChipViewModel: StatusBarUserChipViewModel,
private val centralSurfaces: CentralSurfaces,
private val statusBarWindowStateController: StatusBarWindowStateController,
@@ -301,7 +299,6 @@
statusBarMoveFromCenterAnimationController,
userChipViewModel,
viewUtil,
- sceneContainerFlags,
configurationController,
statusOverlayHoverListenerFactory,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index dd7d282..a141b53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -332,7 +332,6 @@
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
private final SelectedUserInteractor mSelectedUserInteractor;
- @Nullable private KeyguardBypassController mBypassController;
@Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI;
@Nullable private TaskbarDelegate mTaskbarDelegate;
@@ -440,8 +439,7 @@
ShadeLockscreenInteractor shadeLockscreenInteractor,
ShadeExpansionStateManager shadeExpansionStateManager,
BiometricUnlockController biometricUnlockController,
- View notificationContainer,
- KeyguardBypassController bypassController) {
+ View notificationContainer) {
mCentralSurfaces = centralSurfaces;
mBiometricUnlockController = biometricUnlockController;
@@ -452,7 +450,6 @@
shadeExpansionStateManager.addExpansionListener(this);
onPanelExpansionChanged(currentState);
}
- mBypassController = bypassController;
mNotificationContainer = notificationContainer;
if (!DeviceEntryUdfpsRefactor.isEnabled()) {
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
@@ -973,7 +970,6 @@
mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
mKeyguardMessageAreaController.setMessage("");
}
- mBypassController.setAltBouncerShowing(isShowingAlternateBouncer);
mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
if (updateScrim) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 8e8de46..d1189e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,7 +37,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -91,7 +91,6 @@
ShadeInteractor shadeInteractor,
Provider<SceneInteractor> sceneInteractor,
JavaAdapter javaAdapter,
- SceneContainerFlags sceneContainerFlags,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
PrimaryBouncerInteractor primaryBouncerInteractor,
AlternateBouncerInteractor alternateBouncerInteractor
@@ -130,7 +129,7 @@
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
- if (sceneContainerFlags.isEnabled()) {
+ if (SceneContainerFlag.isEnabled()) {
javaAdapter.alwaysCollectFlow(
sceneInteractor.get().isVisible(),
this::onSceneContainerVisibilityChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 541ac48..31c2134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -15,36 +15,55 @@
*/
package com.android.systemui.statusbar.phone
+import android.annotation.StyleRes
import android.app.Dialog
import android.content.Context
-import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.WindowInsets
-import android.view.WindowInsets.Type.InsetsType
-import android.view.WindowInsetsAnimation
import android.view.WindowManager.LayoutParams.MATCH_PARENT
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+import androidx.activity.ComponentDialog
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.onConfigChanged
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
/** A dialog shown as a bottom sheet. */
-open class SystemUIBottomSheetDialog(
+class SystemUIBottomSheetDialog
+@VisibleForTesting
+constructor(
context: Context,
- private val configurationController: ConfigurationController? = null,
- theme: Int = R.style.Theme_SystemUI_Dialog
-) : Dialog(context, theme) {
+ private val coroutineScope: CoroutineScope,
+ private val configurationController: ConfigurationController,
+ private val delegate: DialogDelegate<in Dialog>,
+ private val windowLayout: WindowLayout,
+ theme: Int,
+) : ComponentDialog(context, theme) {
+
+ private var job: Job? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
+ delegate.beforeCreate(this, savedInstanceState)
super.onCreate(savedInstanceState)
setupWindow()
- setupEdgeToEdge()
setCanceledOnTouchOutside(true)
+ delegate.onCreate(this, savedInstanceState)
}
private fun setupWindow() {
@@ -62,60 +81,84 @@
}
}
- private fun setupEdgeToEdge() {
- val edgeToEdgeHorizontally =
- context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
- val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
- val height = WRAP_CONTENT
- window?.setLayout(width, height)
- }
-
override fun onStart() {
super.onStart()
- configurationController?.addCallback(onConfigChanged)
- window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
+ job?.cancel()
+ job =
+ coroutineScope.launch {
+ windowLayout
+ .calculate()
+ .onEach { window?.apply { setLayout(it.width, it.height) } }
+ .launchIn(this)
+ configurationController.onConfigChanged
+ .onEach { delegate.onConfigurationChanged(this@SystemUIBottomSheetDialog, it) }
+ .launchIn(this)
+ }
+ delegate.onStart(this)
}
override fun onStop() {
+ job?.cancel()
+ delegate.onStop(this)
super.onStop()
- configurationController?.removeCallback(onConfigChanged)
- window?.decorView?.setWindowInsetsAnimationCallback(null)
}
- /** Called after any insets change. */
- open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+ override fun onWindowFocusChanged(hasFocus: Boolean) {
+ super.onWindowFocusChanged(hasFocus)
+ delegate.onWindowFocusChanged(this, hasFocus)
+ }
- /** Can be overridden by subclasses to receive config changed events. */
- open fun onConfigurationChanged() {}
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ @Application private val coroutineScope: CoroutineScope,
+ private val defaultWindowLayout: Lazy<WindowLayout.LimitedEdgeToEdge>,
+ private val configurationController: ConfigurationController,
+ ) {
- private val insetsAnimationCallback =
- object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+ fun create(
+ delegate: DialogDelegate<in Dialog>,
+ windowLayout: WindowLayout = defaultWindowLayout.get(),
+ @StyleRes theme: Int = R.style.Theme_SystemUI_Dialog,
+ ): SystemUIBottomSheetDialog =
+ SystemUIBottomSheetDialog(
+ context = context,
+ configurationController = configurationController,
+ coroutineScope = coroutineScope,
+ delegate = delegate,
+ windowLayout = windowLayout,
+ theme = theme,
+ )
+ }
- private var lastInsets: WindowInsets? = null
+ /** [SystemUIBottomSheetDialog] uses this to determine the [android.view.Window] layout. */
+ interface WindowLayout {
- override fun onEnd(animation: WindowInsetsAnimation) {
- lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
- }
+ /** Returns a [Layout] to apply to [android.view.Window.setLayout]. */
+ fun calculate(): Flow<Layout>
- override fun onProgress(
- insets: WindowInsets,
- animations: MutableList<WindowInsetsAnimation>,
- ): WindowInsets {
- lastInsets = insets
- onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
- return insets
- }
+ /** Edge to edge with which doesn't fill the whole space on the large screen. */
+ class LimitedEdgeToEdge
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val configurationController: ConfigurationController,
+ ) : WindowLayout {
- private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
- animations.fold(0) { acc: Int, it -> acc or it.typeMask }
- }
+ override fun calculate(): Flow<Layout> {
+ return configurationController.onConfigChanged
+ .onStart { emit(context.resources.configuration) }
+ .map {
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
- private val onConfigChanged =
- object : ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- super.onConfigChanged(newConfig)
- setupEdgeToEdge()
- onConfigurationChanged()
+ Layout(width, WRAP_CONTENT)
+ }
}
}
+
+ data class Layout(val width: Int, val height: Int)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
index 3b2930f..f4e3eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfig.kt
@@ -18,7 +18,6 @@
import android.os.PersistableBundle
import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
-import android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT
import android.telephony.CarrierConfigManager.KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,11 +42,10 @@
* using the default config for logging purposes.
*
* NOTE to add new keys to be tracked:
- * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig] or [IntCarrierConfig]
- * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config] or
- * [IntCarrierConfig.config]
- * 3. Add the new wrapped public flow to the list of tracked configs, so they are properly updated
- * when a new carrier config comes down
+ * 1. Define a new `private val` wrapping the key using [BooleanCarrierConfig]
+ * 2. Define a public `val` exposing the wrapped flow using [BooleanCarrierConfig.config]
+ * 3. Add the new [BooleanCarrierConfig] to the list of tracked configs, so they are properly
+ * updated when a new carrier config comes down
*/
class SystemUiCarrierConfig
internal constructor(
@@ -68,16 +66,10 @@
/** Flow tracking the [KEY_SHOW_OPERATOR_NAME_IN_STATUSBAR_BOOL] config */
val showOperatorNameInStatusBar: StateFlow<Boolean> = showOperatorName.config
- private val satelliteHysteresisSeconds =
- IntCarrierConfig(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, defaultConfig)
- /** Flow tracking the [KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT] config */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int> = satelliteHysteresisSeconds.config
-
private val trackedConfigs =
listOf(
inflateSignalStrength,
showOperatorName,
- satelliteHysteresisSeconds,
)
/** Ingest a new carrier config, and switch all of the tracked keys over to the new values */
@@ -98,19 +90,15 @@
override fun toString(): String = trackedConfigs.joinToString { it.toString() }
}
-interface CarrierConfig {
- fun update(config: PersistableBundle)
-}
-
/** Extracts [key] from the carrier config, and stores it in a flow */
private class BooleanCarrierConfig(
val key: String,
defaultConfig: PersistableBundle,
-) : CarrierConfig {
+) {
private val _configValue = MutableStateFlow(defaultConfig.getBoolean(key))
val config = _configValue.asStateFlow()
- override fun update(config: PersistableBundle) {
+ fun update(config: PersistableBundle) {
_configValue.value = config.getBoolean(key)
}
@@ -118,20 +106,3 @@
return "$key=${config.value}"
}
}
-
-/** Extracts [key] from the carrier config, and stores it in a flow */
-private class IntCarrierConfig(
- val key: String,
- defaultConfig: PersistableBundle,
-) : CarrierConfig {
- private val _configValue = MutableStateFlow(defaultConfig.getInt(key))
- val config = _configValue.asStateFlow()
-
- override fun update(config: PersistableBundle) {
- _configValue.value = config.getInt(key)
- }
-
- override fun toString(): String {
- return "$key=${config.value}"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 317c063..2278597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -144,9 +144,6 @@
*/
val hasPrioritizedNetworkCapabilities: StateFlow<Boolean>
- /** Duration in seconds of the hysteresis to use when losing satellite connection. */
- val satelliteConnectionHysteresisSeconds: StateFlow<Int>
-
/**
* True if this connection is in emergency callback mode.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 90cdfeb..83d5f2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -227,8 +227,6 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
override suspend fun isInEcmMode(): Boolean = false
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index cb99d11..a532e62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -187,9 +187,6 @@
*/
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false).asStateFlow()
- /** Non-applicable to carrier merged connections. */
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0).asStateFlow()
-
override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
override suspend fun isInEcmMode(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index bb0af77..41559b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
@@ -24,6 +25,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -349,17 +351,29 @@
activeRepo.value.hasPrioritizedNetworkCapabilities.value,
)
- override val satelliteConnectionHysteresisSeconds =
- activeRepo
- .flatMapLatest { it.satelliteConnectionHysteresisSeconds }
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- activeRepo.value.satelliteConnectionHysteresisSeconds.value
- )
-
override suspend fun isInEcmMode(): Boolean = activeRepo.value.isInEcmMode()
+ fun dump(pw: PrintWriter) {
+ val ipw = IndentingPrintWriter(pw, " ")
+
+ ipw.println("MobileConnectionRepository[$subId]")
+ ipw.increaseIndent()
+
+ ipw.println("carrierMerged=${_isCarrierMerged.value}")
+
+ ipw.print("Type (cellular or carrier merged): ")
+ when (activeRepo.value) {
+ is CarrierMergedConnectionRepository -> ipw.println("Carrier merged")
+ is MobileConnectionRepositoryImpl -> ipw.println("Cellular")
+ }
+
+ ipw.increaseIndent()
+ ipw.println("Provider: ${activeRepo.value}")
+ ipw.decreaseIndent()
+
+ ipw.decreaseIndent()
+ }
+
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 2cbe965..b3885d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -450,9 +450,6 @@
.flowOn(bgDispatcher)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val satelliteConnectionHysteresisSeconds: StateFlow<Int> =
- systemUiCarrierConfig.satelliteConnectionHysteresisSeconds
-
class Factory
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index a455db2..5d91ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -26,17 +26,20 @@
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
import android.telephony.TelephonyManager
+import android.util.IndentingPrintWriter
import androidx.annotation.VisibleForTesting
import com.android.internal.telephony.PhoneConstants
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.Dumpable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.res.R
@@ -52,6 +55,8 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -97,8 +102,12 @@
wifiRepository: WifiRepository,
private val fullMobileRepoFactory: FullMobileConnectionRepository.Factory,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-) : MobileConnectionsRepository {
- private var subIdRepositoryCache: MutableMap<Int, FullMobileConnectionRepository> =
+ private val dumpManager: DumpManager,
+) : MobileConnectionsRepository, Dumpable {
+
+ // TODO(b/333912012): for now, we are never invalidating the cache. We can do better though
+ private var subIdRepositoryCache:
+ MutableMap<Int, WeakReference<FullMobileConnectionRepository>> =
mutableMapOf()
private val defaultNetworkName =
@@ -109,6 +118,10 @@
private val networkNameSeparator: String =
context.getString(R.string.status_bar_network_name_separator)
+ init {
+ dumpManager.registerNormalDumpable("MobileConnectionsRepository", this)
+ }
+
private val carrierMergedSubId: StateFlow<Int?> =
combine(
wifiRepository.wifiNetwork,
@@ -283,8 +296,10 @@
getOrCreateRepoForSubId(subId)
private fun getOrCreateRepoForSubId(subId: Int) =
- subIdRepositoryCache[subId]
- ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+ subIdRepositoryCache[subId]?.get()
+ ?: createRepositoryForSubId(subId).also {
+ subIdRepositoryCache[subId] = WeakReference(it)
+ }
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
@@ -374,9 +389,8 @@
}
private fun updateRepos(newInfos: List<SubscriptionModel>) {
- dropUnusedReposFromCache(newInfos)
subIdRepositoryCache.forEach { (subId, repo) ->
- repo.setIsCarrierMerged(isCarrierMerged(subId))
+ repo.get()?.setIsCarrierMerged(isCarrierMerged(subId))
}
}
@@ -384,13 +398,6 @@
return subId == carrierMergedSubId.value
}
- private fun dropUnusedReposFromCache(newInfos: List<SubscriptionModel>) {
- // Remove any connection repository from the cache that isn't in the new set of IDs. They
- // will get garbage collected once their subscribers go away
- subIdRepositoryCache =
- subIdRepositoryCache.filter { checkSub(it.key, newInfos) }.toMutableMap()
- }
-
/**
* True if the checked subId is in the list of current subs or the active mobile data subId
*
@@ -422,6 +429,22 @@
profileClass = profileClass,
)
+ override fun dump(pw: PrintWriter, args: Array<String>) {
+ val ipw = IndentingPrintWriter(pw, " ")
+ ipw.println("Connection cache:")
+
+ ipw.increaseIndent()
+ subIdRepositoryCache.entries.forEach { (subId, repo) ->
+ ipw.println("$subId: ${repo.get()}")
+ }
+ ipw.decreaseIndent()
+
+ ipw.println("Connections (${subIdRepositoryCache.size} total):")
+ ipw.increaseIndent()
+ subIdRepositoryCache.values.forEach { it.get()?.dump(ipw) }
+ ipw.decreaseIndent()
+ }
+
companion object {
private const val LOGGING_PREFIX = "Repo"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index cbebfd0..ed9e405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -35,25 +35,18 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.util.kotlin.pairwiseBy
-import kotlin.time.DurationUnit
-import kotlin.time.toDuration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
interface MobileIconInteractor {
/** The table log created for this connection */
@@ -269,43 +262,6 @@
MutableStateFlow(false).asStateFlow()
}
- private val hysteresisActive = MutableStateFlow(false)
-
- private val isNonTerrestrialWithHysteresis: StateFlow<Boolean> =
- combine(isNonTerrestrial, hysteresisActive) { isNonTerrestrial, hysteresisActive ->
- if (hysteresisActive) {
- true
- } else {
- isNonTerrestrial
- }
- }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnName = "isNonTerrestrialWithHysteresis",
- columnPrefix = "",
- initialValue = Flags.carrierEnabledSatelliteFlag(),
- )
- .stateIn(scope, SharingStarted.Eagerly, Flags.carrierEnabledSatelliteFlag())
-
- private val lostSatelliteConnection =
- isNonTerrestrial.pairwiseBy { old, new -> hysteresisActive.value = old && !new }
-
- init {
- scope.launch { lostSatelliteConnection.collect() }
- scope.launch {
- hysteresisActive.collectLatest {
- if (it) {
- delay(
- connectionRepository.satelliteConnectionHysteresisSeconds.value.toDuration(
- DurationUnit.SECONDS
- )
- )
- hysteresisActive.value = false
- }
- }
- }
- }
-
override val isRoaming: StateFlow<Boolean> =
combine(
connectionRepository.carrierNetworkChangeActive,
@@ -407,7 +363,7 @@
showExclamationMark.value,
carrierNetworkChangeActive.value,
)
- isNonTerrestrialWithHysteresis
+ isNonTerrestrial
.flatMapLatest { ntn ->
if (ntn) {
satelliteIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index a0c5618..5f08afd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -128,6 +128,8 @@
mobileGroupView,
dotView,
)
+
+ view.requestLayout()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 08ed030..054116d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -41,6 +43,7 @@
val repo: DeviceBasedSatelliteRepository,
iconsInteractor: MobileIconsInteractor,
deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ wifiInteractor: WifiInteractor,
@Application scope: CoroutineScope,
) {
/** Must be observed by any UI showing Satellite iconography */
@@ -73,6 +76,9 @@
val isDeviceProvisioned: Flow<Boolean> = deviceProvisioningInteractor.isDeviceProvisioned
+ val isWifiActive: Flow<Boolean> =
+ wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active }
+
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 40641be..a0291b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -59,9 +59,10 @@
combine(
interactor.isSatelliteAllowed,
interactor.isDeviceProvisioned,
+ interactor.isWifiActive,
airplaneModeRepository.isAirplaneMode
- ) { isSatelliteAllowed, isDeviceProvisioned, isAirplaneMode ->
- isSatelliteAllowed && isDeviceProvisioned && !isAirplaneMode
+ ) { isSatelliteAllowed, isDeviceProvisioned, isWifiActive, isAirplaneMode ->
+ isSatelliteAllowed && isDeviceProvisioned && !isWifiActive && !isAirplaneMode
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
index 0d3682c..fbbd2b9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/UnfoldTransitionRepository.kt
@@ -15,9 +15,11 @@
*/
package com.android.systemui.unfold.data.repository
+import androidx.annotation.FloatRange
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
@@ -42,6 +44,10 @@
sealed class UnfoldTransitionStatus {
/** Status that is sent when fold or unfold transition is in started state */
data object TransitionStarted : UnfoldTransitionStatus()
+ /** Status that is sent while fold or unfold transition is in progress */
+ data class TransitionInProgress(
+ @FloatRange(from = 0.0, to = 1.0) val progress: Float,
+ ) : UnfoldTransitionStatus()
/** Status that is sent when fold or unfold transition is finished */
data object TransitionFinished : UnfoldTransitionStatus()
}
@@ -66,6 +72,10 @@
trySend(TransitionStarted)
}
+ override fun onTransitionProgress(progress: Float) {
+ trySend(TransitionInProgress(progress))
+ }
+
override fun onTransitionFinished() {
trySend(TransitionFinished)
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index 3e2e564..a8e4496 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -17,10 +17,14 @@
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionInProgress
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
/**
* Contains business-logic related to fold-unfold transitions while interacting with
@@ -30,6 +34,8 @@
/** Returns availability of fold/unfold transitions on the device */
val isAvailable: Boolean
+ val unfoldProgress: Flow<Float>
+
/** Suspends and waits for a fold/unfold transition to finish */
suspend fun waitForTransitionFinish()
@@ -44,6 +50,11 @@
override val isAvailable: Boolean
get() = repository.isAvailable
+ override val unfoldProgress: Flow<Float> =
+ repository.transitionStatus
+ .map { (it as? TransitionInProgress)?.progress ?: 1f }
+ .distinctUntilChanged()
+
override suspend fun waitForTransitionFinish() {
repository.transitionStatus.filter { it is TransitionFinished }.first()
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
new file mode 100644
index 0000000..ee00e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/ReduceBrightColorsControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.qs.ReduceBrightColorsController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun ReduceBrightColorsController.isEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val callback =
+ object : ReduceBrightColorsController.Listener {
+ override fun onActivated(activated: Boolean) {
+ trySend(activated)
+ }
+ }
+ addCallback(callback)
+ awaitClose { removeCallback(callback) }
+ }
+ .onStart { emit(isReduceBrightColorsActivated) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 7edb5a5..df19013 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -17,6 +17,7 @@
package com.android.systemui.volume;
import android.media.AudioManager;
+import android.util.MathUtils;
import android.view.View;
/**
@@ -46,4 +47,27 @@
if (v == null || (v.getVisibility() == View.VISIBLE) == vis) return;
v.setVisibility(vis ? View.VISIBLE : View.GONE);
}
+
+ /**
+ * Translates a value from one range to another.
+ *
+ * ```
+ * Given: currentValue=3, currentRange=[0, 8], targetRange=[0, 100]
+ * Result: 37.5
+ * ```
+ */
+ public static float translateToRange(float value,
+ float valueRangeStart,
+ float valueRangeEnd,
+ float targetRangeStart,
+ float targetRangeEnd) {
+ float currentRangeLength = valueRangeEnd - valueRangeStart;
+ float targetRangeLength = targetRangeEnd - targetRangeStart;
+ if (currentRangeLength == 0f || targetRangeLength == 0f) {
+ return targetRangeStart;
+ }
+ float valueFraction = (value - valueRangeStart) / currentRangeLength;
+ return MathUtils.constrain(targetRangeStart + valueFraction * targetRangeLength,
+ targetRangeStart, targetRangeEnd);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1688b0b..2245541 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -137,13 +137,13 @@
import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
import com.android.systemui.volume.ui.navigation.VolumeNavigator;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import dagger.Lazy;
+
/**
* Visual presentation of the volume dialog.
*
@@ -166,6 +166,7 @@
private static final int DRAWER_ANIMATION_DURATION_SHORT = 175;
private static final int DRAWER_ANIMATION_DURATION = 250;
+ private static final int DISPLAY_RANGE_MULTIPLIER = 100;
/** Shows volume dialog show animation. */
private static final String TYPE_SHOW = "show";
@@ -826,12 +827,14 @@
writer.print(" mSilentMode: "); writer.println(mSilentMode);
}
- private static int getImpliedLevel(SeekBar seekBar, int progress) {
- final int m = seekBar.getMax();
- final int n = m / 100 - 1;
- final int level = progress == 0 ? 0
- : progress == m ? (m / 100) : (1 + (int) ((progress / (float) m) * n));
- return level;
+ private static int getVolumeFromProgress(StreamState state, SeekBar seekBar, int progress) {
+ return (int) Util.translateToRange(progress, seekBar.getMin(), seekBar.getMax(),
+ state.levelMin, state.levelMax);
+ }
+
+ private static int getProgressFromVolume(StreamState state, SeekBar seekBar, int volume) {
+ return (int) Util.translateToRange(volume, state.levelMin, state.levelMax, seekBar.getMin(),
+ seekBar.getMax());
}
@SuppressLint("InflateParams")
@@ -854,6 +857,8 @@
addSliderHapticsToRow(row);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.number = row.view.findViewById(R.id.volume_number);
+ row.slider.setAccessibilityDelegate(
+ new VolumeDialogSeekBarAccessibilityDelegate(DISPLAY_RANGE_MULTIPLIER));
row.anim = null;
@@ -1916,12 +1921,12 @@
: false;
// update slider max
- final int max = ss.levelMax * 100;
+ final int max = ss.levelMax * DISPLAY_RANGE_MULTIPLIER;
if (max != row.slider.getMax()) {
row.slider.setMax(max);
}
// update slider min
- final int min = ss.levelMin * 100;
+ final int min = ss.levelMin * DISPLAY_RANGE_MULTIPLIER;
if (min != row.slider.getMin()) {
row.slider.setMin(min);
}
@@ -2069,7 +2074,7 @@
return; // don't update if user is sliding
}
final int progress = row.slider.getProgress();
- final int level = getImpliedLevel(row.slider, progress);
+ final int level = getVolumeFromProgress(row.ss, row.slider, progress);
final boolean rowVisible = row.view.getVisibility() == VISIBLE;
final boolean inGracePeriod = (SystemClock.uptimeMillis() - row.userAttempt)
< USER_ATTEMPT_GRACE_PERIOD;
@@ -2085,7 +2090,7 @@
return; // don't clamp if visible
}
}
- final int newProgress = vlevel * 100;
+ final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel);
if (progress != newProgress) {
if (mShowing && rowVisible) {
// animate!
@@ -2530,13 +2535,13 @@
+ " onProgressChanged " + progress + " fromUser=" + fromUser);
if (!fromUser) return;
if (mRow.ss.levelMin > 0) {
- final int minProgress = mRow.ss.levelMin * 100;
+ final int minProgress = getProgressFromVolume(mRow.ss, seekBar, mRow.ss.levelMin);
if (progress < minProgress) {
seekBar.setProgress(minProgress);
progress = minProgress;
}
}
- final int userLevel = getImpliedLevel(seekBar, progress);
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, progress);
if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
mRow.userAttempt = SystemClock.uptimeMillis();
if (mRow.requestedLevel != userLevel) {
@@ -2569,7 +2574,7 @@
}
mRow.tracking = false;
mRow.userAttempt = SystemClock.uptimeMillis();
- final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
+ final int userLevel = getVolumeFromProgress(mRow.ss, seekBar, seekBar.getProgress());
Events.writeEvent(Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
if (mRow.ss.level != userLevel) {
mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
new file mode 100644
index 0000000..cd31a95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogSeekBarAccessibilityDelegate.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.volume
+
+import android.os.Bundle
+import android.view.View
+import android.view.View.AccessibilityDelegate
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.SeekBar
+import com.android.internal.R
+
+class VolumeDialogSeekBarAccessibilityDelegate(
+ private val accessibilityStep: Int,
+) : AccessibilityDelegate() {
+
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
+ require(host is SeekBar) { "This class only works with the SeekBar" }
+ val seekBar: SeekBar = host
+ if (
+ action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD ||
+ action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+ ) {
+ var increment = accessibilityStep
+ if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
+ increment = -increment
+ }
+
+ return super.performAccessibilityAction(
+ host,
+ R.id.accessibilityActionSetProgress,
+ Bundle().apply {
+ putFloat(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE,
+ (seekBar.progress + increment).coerceIn(seekBar.min, seekBar.max).toFloat(),
+ )
+ },
+ )
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
index 9f9275b..e5c5a65 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -16,13 +16,10 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
-import com.android.systemui.common.shared.model.Color
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
data class SpatialAudioButtonViewModel(
val model: SpatialAudioEnabledModel,
val button: ToggleButtonViewModel,
- val iconColor: Color,
- val labelColor: Color,
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4ecdd46..b5e9ed2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -18,7 +18,6 @@
import android.content.Context
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
@@ -79,26 +78,7 @@
val isChecked = isEnabled == currentIsEnabled
val buttonViewModel: ToggleButtonViewModel =
isEnabled.toViewModel(isChecked)
- SpatialAudioButtonViewModel(
- button = buttonViewModel,
- model = isEnabled,
- iconColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnPrimaryContainer
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- labelColor =
- Color.Attribute(
- if (isChecked) {
- com.android.internal.R.attr.materialColorOnSurface
- } else {
- com.android.internal.R.attr.materialColorOnSurfaceVariant
- }
- ),
- )
+ SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
}
}
.stateIn(scope, SharingStarted.Eagerly, emptyList())
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index a249961..319b615 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -205,9 +205,9 @@
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false, false, false));
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 9d81b96..99b5a4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -272,9 +272,9 @@
assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
when(mSmallClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
when(mLargeClockController.getConfig())
- .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false));
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true, false, true));
verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
listenerArgumentCaptor.getValue().onCurrentClockChanged();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 11fe862..b2828a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -98,7 +98,7 @@
public void updatePosition_primaryClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", false, true, false));
mController.updatePosition(10, 15, 20f, true);
@@ -113,7 +113,7 @@
public void updatePosition_alternateClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
- when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true));
+ when(mockClock.getConfig()).thenReturn(new ClockConfig("MOCK", "", "", true, true, false));
mController.updatePosition(10, 15, 20f, true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5af0c1f..fde45d3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -52,7 +52,6 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -158,6 +157,7 @@
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.internal.util.reflection.FieldSetter;
@@ -809,31 +809,6 @@
}
@Test
- public void whenFaceAuthenticated_biometricAuthenticatedCallback_beforeUpdatingFpState() {
- // GIVEN listening for UDFPS fingerprint
- when(mAuthController.isUdfpsSupported()).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
- mTestableLooper.processAllMessages();
- keyguardIsVisible();
- final CancellationSignal fpCancel = spy(mKeyguardUpdateMonitor.mFingerprintCancelSignal);
- mKeyguardUpdateMonitor.mFingerprintCancelSignal = fpCancel;
-
- // WHEN face is authenticated
- when(mFaceAuthInteractor.isAuthenticated()).thenReturn(true);
- when(mFaceAuthInteractor.isFaceAuthStrong()).thenReturn(true);
- when(mFaceAuthInteractor.isLockedOut()).thenReturn(false);
- mKeyguardUpdateMonitor.onFaceAuthenticated(0, true);
- mTestableLooper.processAllMessages();
-
- // THEN verify keyguardUpdateMonitorCallback receives an onAuthenticated callback
- // before cancelling the fingerprint request
- InOrder inOrder = inOrder(mTestCallback, fpCancel);
- inOrder.verify(mTestCallback).onBiometricAuthenticated(
- eq(0), eq(BiometricSourceType.FACE), eq(true));
- inOrder.verify(fpCancel).cancel();
- }
-
- @Test
public void whenDetectFingerprint_biometricDetectCallback() {
ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
@@ -2158,7 +2133,7 @@
null /* trustGrantedMessages */);
// THEN onTrustChanged is called FIRST
- final InOrder inOrder = inOrder(callback);
+ final InOrder inOrder = Mockito.inOrder(callback);
inOrder.verify(callback).onTrustChanged(eq(mSelectedUserInteractor.getSelectedUserId()));
// AND THEN onTrustGrantedForCurrentUser callback called
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
index f924ab4..bcea411 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java
@@ -36,6 +36,7 @@
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageView;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
@@ -78,6 +79,7 @@
protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
protected @Mock LockIconView mLockIconView;
+ protected @Mock ImageView mLockIcon;
protected @Mock AnimatedStateListDrawable mIconDrawable;
protected @Mock Context mContext;
protected @Mock Resources mResources;
@@ -146,8 +148,10 @@
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
- mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+ if (!Flags.sceneContainer()) {
+ mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR);
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
+ }
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false);
@@ -172,8 +176,7 @@
mFeatureFlags,
mPrimaryBouncerInteractor,
mContext,
- () -> mDeviceEntryInteractor,
- mKosmos.getFakeSceneContainerFlags()
+ () -> mDeviceEntryInteractor
);
}
@@ -225,6 +228,7 @@
protected void setupLockIconViewMocks() {
when(mLockIconView.getResources()).thenReturn(mResources);
when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mLockIconView.getLockIcon()).thenReturn(mLockIcon);
}
protected void resetLockIconView() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
index 8689842..255c7d9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java
@@ -43,6 +43,7 @@
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Test;
@@ -373,7 +374,6 @@
@Test
public void longPress_showBouncer_sceneContainerNotEnabled() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(false);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -385,9 +385,9 @@
}
@Test
+ @EnableSceneContainer
public void longPress_showBouncer() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -399,9 +399,9 @@
}
@Test
+ @EnableSceneContainer
public void longPress_falsingTriggered_doesNotShowBouncer() {
init(/* useMigrationFlag= */ false);
- mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
// WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index e076420..44207a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -477,9 +477,8 @@
});
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -594,10 +593,10 @@
final float expectedY = (int) (windowBounds.exactCenterY() + expectedOffset
- defaultMagnificationWindowSize / 2);
- // This is called 5 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
+ // This is called 4 times when (1) first creating WindowlessMirrorWindow (2) SurfaceView is
// created and we place the mirrored content as a child of the SurfaceView
- // (3) the animation starts (4) the animation updates (5) the animation ends
- verify(mTransaction, times(5))
+ // (3) the animation starts (4) the animation updates
+ verify(mTransaction, times(4))
.setPosition(any(SurfaceControl.class), eq(expectedX), eq(expectedY));
}
@@ -788,9 +787,8 @@
waitForIdleSync();
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once in {@link ValueAnimator#end()}
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -832,10 +830,8 @@
deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
// Verify the method is called in
- // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once and
- // {@link Animator.AnimatorListener#onAnimationEnd} once when running the animation at
- // the final duration time.
- verify(mSpyController, times(2)).updateWindowMagnificationInternal(
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate} once
+ verify(mSpyController).updateWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index a88654b..01e4d58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -46,6 +46,7 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -459,6 +460,7 @@
final float targetCenterX = sourceBoundsCaptor.getValue().exactCenterX() + 10;
final float targetCenterY = sourceBoundsCaptor.getValue().exactCenterY() + 10;
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
targetCenterX, targetCenterY, mAnimationCallback);
@@ -491,6 +493,7 @@
final float centerX = sourceBoundsCaptor.getValue().exactCenterX();
final float centerY = sourceBoundsCaptor.getValue().exactCenterY();
+ reset(mWindowMagnifierCallback);
mInstrumentation.runOnMainSync(() -> {
mWindowMagnificationController.moveWindowMagnifierToPosition(
centerX + 10, centerY + 10, mAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
index 8685384..2f4999b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/InputSessionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/InputSessionTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index a127631..1e3b556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.dreams.touch;
+package com.android.systemui.ambient.touch;
import static com.google.common.truth.Truth.assertThat;
@@ -43,7 +43,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
+import com.android.systemui.ambient.touch.dagger.InputSessionComponent;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.display.DisplayHelper;
@@ -67,7 +67,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
+public class TouchMonitorTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -78,7 +78,7 @@
private final InputSession mInputSession;
private final Lifecycle mLifecycle;
private final LifecycleOwner mLifecycleOwner;
- private final DreamOverlayTouchMonitor mMonitor;
+ private final TouchMonitor mMonitor;
private final DefaultLifecycleObserver mLifecycleObserver;
private final InputChannelCompat.InputEventListener mEventListener;
private final GestureDetector.OnGestureListener mGestureListener;
@@ -88,7 +88,7 @@
private final Rect mDisplayBounds = Mockito.mock(Rect.class);
private final IWindowManager mIWindowManager;
- Environment(Set<DreamTouchHandler> handlers) {
+ Environment(Set<TouchHandler> handlers) {
mLifecycle = Mockito.mock(Lifecycle.class);
mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
mIWindowManager = Mockito.mock(IWindowManager.class);
@@ -104,7 +104,7 @@
mDisplayHelper = Mockito.mock(DisplayHelper.class);
when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
.thenReturn(mDisplayBounds);
- mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+ mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor,
mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
mMonitor.init();
@@ -157,7 +157,7 @@
@Test
public void testReportedDisplayBounds() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -169,8 +169,8 @@
// Verify display bounds passed into TouchHandler#getTouchInitiationRegion
verify(touchHandler).getTouchInitiationRegion(
eq(environment.getDisplayBounds()), any(), any());
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
// Verify that display bounds provided from TouchSession#getBounds
@@ -180,7 +180,7 @@
@Test
public void testEntryTouchZone() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Rect touchArea = new Rect(4, 4, 8 , 8);
doAnswer(invocation -> {
@@ -208,10 +208,10 @@
@Test
public void testSessionCount() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Rect touchArea = new Rect(4, 4, 8 , 8);
- final DreamTouchHandler unzonedTouchHandler = createTouchHandler();
+ final TouchHandler unzonedTouchHandler = createTouchHandler();
doAnswer(invocation -> {
final Region region = (Region) invocation.getArguments()[1];
region.set(touchArea);
@@ -227,13 +227,13 @@
when(initialEvent.getY()).thenReturn(1.0f);
environment.publishInputEvent(initialEvent);
- ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
- DreamTouchHandler.TouchSession.class);
+ ArgumentCaptor<TouchHandler.TouchSession> touchSessionCaptor = ArgumentCaptor.forClass(
+ TouchHandler.TouchSession.class);
// Make sure only one active session.
{
verify(unzonedTouchHandler).onSessionStart(touchSessionCaptor.capture());
- final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+ final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
assertThat(touchSession.getActiveSessionCount()).isEqualTo(1);
touchSession.pop();
environment.executeAll();
@@ -247,7 +247,7 @@
// Make sure there are two active sessions.
{
verify(touchHandler).onSessionStart(touchSessionCaptor.capture());
- final DreamTouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
+ final TouchHandler.TouchSession touchSession = touchSessionCaptor.getValue();
assertThat(touchSession.getActiveSessionCount()).isEqualTo(2);
touchSession.pop();
}
@@ -256,7 +256,7 @@
@Test
public void testNoActiveSessionWhenHandlerDisabled() {
- final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
// disable the handler
when(touchHandler.isEnabled()).thenReturn(false);
@@ -274,7 +274,7 @@
@Test
public void testInputEventPropagation() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -294,7 +294,7 @@
@Test
public void testInputEventPropagationAfterRemoval() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -303,7 +303,7 @@
environment.publishInputEvent(initialEvent);
// Ensure session started
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
final InputChannelCompat.InputEventListener eventListener =
registerInputEventListener(session);
@@ -318,7 +318,7 @@
@Test
public void testInputGesturePropagation() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -337,7 +337,7 @@
@Test
public void testGestureConsumption() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -360,8 +360,8 @@
@Test
public void testBroadcast() {
- final DreamTouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler touchHandler2 = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler2 = createTouchHandler();
when(touchHandler2.isEnabled()).thenReturn(true);
final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2)
@@ -386,7 +386,7 @@
@Test
public void testPush() throws InterruptedException, ExecutionException {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -394,13 +394,13 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
final InputChannelCompat.InputEventListener eventListener =
registerInputEventListener(session);
- final ListenableFuture<DreamTouchHandler.TouchSession> frontSessionFuture = session.push();
+ final ListenableFuture<TouchHandler.TouchSession> frontSessionFuture = session.push();
environment.executeAll();
- final DreamTouchHandler.TouchSession frontSession = frontSessionFuture.get();
+ final TouchHandler.TouchSession frontSession = frontSessionFuture.get();
final InputChannelCompat.InputEventListener frontEventListener =
registerInputEventListener(frontSession);
@@ -412,10 +412,10 @@
Mockito.clearInvocations(eventListener, frontEventListener);
- ListenableFuture<DreamTouchHandler.TouchSession> sessionFuture = frontSession.pop();
+ ListenableFuture<TouchHandler.TouchSession> sessionFuture = frontSession.pop();
environment.executeAll();
- DreamTouchHandler.TouchSession returnedSession = sessionFuture.get();
+ TouchHandler.TouchSession returnedSession = sessionFuture.get();
assertThat(session == returnedSession).isTrue();
environment.executeAll();
@@ -429,10 +429,10 @@
@Test
public void testPop() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler.TouchSession.Callback callback =
- Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -440,7 +440,7 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
session.registerCallback(callback);
session.pop();
environment.executeAll();
@@ -450,7 +450,7 @@
@Test
public void testPauseWithNoActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -464,7 +464,7 @@
@Test
public void testDeferredPauseWithActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -481,8 +481,8 @@
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -502,7 +502,7 @@
@Test
public void testDestroyWithActiveSessions() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -519,8 +519,8 @@
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
- final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -535,19 +535,19 @@
@Test
public void testPilfering() {
- final DreamTouchHandler touchHandler1 = createTouchHandler();
- final DreamTouchHandler touchHandler2 = createTouchHandler();
+ final TouchHandler touchHandler1 = createTouchHandler();
+ final TouchHandler touchHandler2 = createTouchHandler();
final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2)
.collect(Collectors.toCollection(HashSet::new)));
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session1 = captureSession(touchHandler1);
+ final TouchHandler.TouchSession session1 = captureSession(touchHandler1);
final GestureDetector.OnGestureListener gestureListener1 =
registerGestureListener(session1);
- final DreamTouchHandler.TouchSession session2 = captureSession(touchHandler2);
+ final TouchHandler.TouchSession session2 = captureSession(touchHandler2);
final GestureDetector.OnGestureListener gestureListener2 =
registerGestureListener(session2);
when(gestureListener2.onDown(any())).thenReturn(true);
@@ -568,10 +568,10 @@
@Test
public void testOnRemovedCallbackOnStopMonitoring() {
- final DreamTouchHandler touchHandler = createTouchHandler();
+ final TouchHandler touchHandler = createTouchHandler();
- final DreamTouchHandler.TouchSession.Callback callback =
- Mockito.mock(DreamTouchHandler.TouchSession.Callback.class);
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
@@ -579,7 +579,7 @@
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
- final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
session.registerCallback(callback);
environment.executeAll();
@@ -593,19 +593,19 @@
verify(callback).onRemoved();
}
- public GestureDetector.OnGestureListener registerGestureListener(DreamTouchHandler handler) {
+ private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
- final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(handler).onSessionStart(sessionCaptor.capture());
sessionCaptor.getValue().registerGestureListener(gestureListener);
return gestureListener;
}
- public GestureDetector.OnGestureListener registerGestureListener(
- DreamTouchHandler.TouchSession session) {
+ private GestureDetector.OnGestureListener registerGestureListener(
+ TouchHandler.TouchSession session) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
session.registerGestureListener(gestureListener);
@@ -613,8 +613,8 @@
return gestureListener;
}
- public InputChannelCompat.InputEventListener registerInputEventListener(
- DreamTouchHandler.TouchSession session) {
+ private InputChannelCompat.InputEventListener registerInputEventListener(
+ TouchHandler.TouchSession session) {
final InputChannelCompat.InputEventListener eventListener = Mockito.mock(
InputChannelCompat.InputEventListener.class);
session.registerInputListener(eventListener);
@@ -622,20 +622,20 @@
return eventListener;
}
- public DreamTouchHandler.TouchSession captureSession(DreamTouchHandler handler) {
- final ArgumentCaptor<DreamTouchHandler.TouchSession> sessionCaptor =
- ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
+ private TouchHandler.TouchSession captureSession(TouchHandler handler) {
+ final ArgumentCaptor<TouchHandler.TouchSession> sessionCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
verify(handler).onSessionStart(sessionCaptor.capture());
return sessionCaptor.getValue();
}
- public InputChannelCompat.InputEventListener registerInputEventListener(
- DreamTouchHandler handler) {
+ private InputChannelCompat.InputEventListener registerInputEventListener(
+ TouchHandler handler) {
return registerInputEventListener(captureSession(handler));
}
- private DreamTouchHandler createTouchHandler() {
- final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+ private TouchHandler createTouchHandler() {
+ final TouchHandler touchHandler = Mockito.mock(TouchHandler.class);
// enable the handler by default
when(touchHandler.isEnabled()).thenReturn(true);
return touchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index f490f3c..cbad133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -41,7 +41,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
@@ -109,7 +108,6 @@
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 30c5e6e..d3cc232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -78,7 +78,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -239,7 +238,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 238a76e..415da02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -77,7 +77,6 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -236,7 +235,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
new file mode 100644
index 0000000..8a1a082
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AudioSharingInteractorTest : SysuiTestCase() {
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val bluetoothState = MutableStateFlow(false)
+ private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+ @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+ @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
+ @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+ @Mock private lateinit var deviceItem: DeviceItem
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var audioSharingInteractor: AudioSharingInteractor
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
+ whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+ audioSharingInteractor =
+ AudioSharingInteractor(
+ localBluetoothManager,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun testButtonStateUpdate_bluetoothOff_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_noDevice_returnGone() {
+ testScope.runTest {
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_isBroadcasting_returnSharingAudio() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf())
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button_sharing
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasSource_returnGone() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
+ }
+ }
+
+ @Test
+ fun testButtonStateUpdate_hasActiveDevice_returnAudioSharing() {
+ testScope.runTest {
+ whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(false)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ )
+ .thenReturn(false)
+ whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
+
+ val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ bluetoothState.value = true
+ deviceItemUpdate.emit(listOf(deviceItem))
+ runCurrent()
+
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingButtonState.Visible(
+ R.string.quick_settings_bluetooth_audio_sharing_button
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
index a8f82ed..6fe7d86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -41,7 +42,8 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class BluetoothStateInteractorTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
- private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
@@ -52,7 +54,12 @@
@Before
fun setUp() {
bluetoothStateInteractor =
- BluetoothStateInteractor(localBluetoothManager, logger, testScope.backgroundScope)
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ logger,
+ testScope.backgroundScope,
+ testDispatcher
+ )
`when`(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
}
@@ -61,7 +68,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(true)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isTrue()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isTrue()
}
}
@@ -70,7 +77,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- assertThat(bluetoothStateInteractor.isBluetoothEnabled).isFalse()
+ assertThat(bluetoothStateInteractor.isBluetoothEnabled()).isFalse()
}
}
@@ -79,7 +86,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = true
+ bluetoothStateInteractor.setBluetoothEnabled(true)
verify(bluetoothAdapter).enable()
verify(logger)
.logBluetoothState(BluetoothStateStage.BLUETOOTH_STATE_VALUE_SET, true.toString())
@@ -91,7 +98,7 @@
testScope.runTest {
`when`(bluetoothAdapter.isEnabled).thenReturn(false)
- bluetoothStateInteractor.isBluetoothEnabled = false
+ bluetoothStateInteractor.setBluetoothEnabled(false)
verify(bluetoothAdapter, never()).enable()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
index 12dfe97..62c98b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt
@@ -110,7 +110,6 @@
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -211,7 +210,6 @@
BluetoothTileDialogDelegate(
uiProperties,
CONTENT_HEIGHT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -267,7 +265,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
cachedHeight,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -291,7 +288,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
@@ -315,7 +311,6 @@
BluetoothTileDialogDelegate(
BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
MATCH_PARENT,
- ENABLED,
bluetoothTileDialogCallback,
{},
dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 6d99c5b..b05d959 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -52,7 +52,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
@@ -74,7 +73,7 @@
@Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var bluetoothAutoOnInteractor: BluetoothAutoOnInteractor
+ @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -92,6 +91,8 @@
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
+
@Mock
private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
BluetoothTileDialogDelegate.Factory
@@ -115,7 +116,12 @@
bluetoothTileDialogViewModel =
BluetoothTileDialogViewModel(
deviceItemInteractor,
- bluetoothStateInteractor,
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ bluetoothTileDialogLogger,
+ testScope.backgroundScope,
+ dispatcher
+ ),
// TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
BluetoothAutoOnInteractor(
BluetoothAutoOnRepository(
@@ -125,6 +131,7 @@
dispatcher
)
),
+ audioSharingInteractor,
mDialogTransitionAnimator,
activityStarter,
uiEventLogger,
@@ -135,20 +142,9 @@
mBluetoothTileDialogDelegateDelegateFactory
)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
- whenever(bluetoothStateInteractor.bluetoothStateUpdate)
- .thenReturn(MutableStateFlow(null).asStateFlow())
whenever(deviceItemInteractor.deviceItemUpdateRequest)
.thenReturn(MutableStateFlow(Unit).asStateFlow())
- whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
- whenever(
- mBluetoothTileDialogDelegateDelegateFactory.create(
- any(),
- anyInt(),
- ArgumentMatchers.anyBoolean(),
- any(),
- any()
- )
- )
+ whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
.thenReturn(bluetoothTileDialogDelegate)
whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
whenever(sysuiDialog.context).thenReturn(mContext)
@@ -159,6 +155,8 @@
whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
+ whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
+ .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
}
@Test
@@ -201,15 +199,6 @@
}
@Test
- fun testShowDialog_withBluetoothStateValue() {
- testScope.runTest {
- bluetoothTileDialogViewModel.showDialog(null)
-
- verify(bluetoothStateInteractor).bluetoothStateUpdate
- }
- }
-
- @Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index eb735cb..daf4a3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 The Android Open Source Project
+ * 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.
@@ -281,7 +281,7 @@
override fun isFilterMatched(
context: Context,
cachedDevice: CachedBluetoothDevice,
- audioManager: AudioManager?
+ audioManager: AudioManager
) = isFilterMatchFunc(cachedDevice)
override fun create(context: Context, cachedDevice: CachedBluetoothDevice) = deviceItem
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
new file mode 100644
index 0000000..d118cc7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogDelegateTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.display.ui.view
+
+import android.app.Dialog
+import android.graphics.Insets
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.Window
+import android.view.WindowInsets
+import android.view.WindowInsetsAnimation
+import androidx.test.filters.SmallTest
+import com.android.app.animation.Interpolators
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MirroringConfirmationDialogDelegateTest : SysuiTestCase() {
+
+ private lateinit var underTest: MirroringConfirmationDialogDelegate
+
+ private val onStartMirroringCallback = mock<View.OnClickListener>()
+ private val onCancelCallback = mock<View.OnClickListener>()
+ private val windowDecorView: View = mock {}
+ private val windowInsetsAnimationCallbackCaptor =
+ ArgumentCaptor.forClass(WindowInsetsAnimation.Callback::class.java)
+ private val dialog: Dialog =
+ mock<Dialog> {
+ var view: View? = null
+ whenever(setContentView(any<Int>())).then {
+ view =
+ LayoutInflater.from(this@MirroringConfirmationDialogDelegateTest.context)
+ .inflate(it.arguments[0] as Int, null, false)
+ Unit
+ }
+ whenever(requireViewById<View>(any<Int>())).then {
+ view?.requireViewById(it.arguments[0] as Int)
+ }
+ val window: Window = mock { whenever(decorView).thenReturn(windowDecorView) }
+ whenever(this.window).thenReturn(window)
+ }
+
+ @Before
+ fun setUp() {
+ underTest =
+ MirroringConfirmationDialogDelegate(
+ context = context,
+ showConcurrentDisplayInfo = false,
+ onStartMirroringClickListener = onStartMirroringCallback,
+ onCancelMirroring = onCancelCallback,
+ navbarBottomInsetsProvider = { 0 },
+ )
+ }
+
+ @Test
+ fun startMirroringButton_clicked_callsCorrectCallback() {
+ underTest.onCreate(dialog, null)
+
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ verify(onStartMirroringCallback).onClick(any())
+ verify(onCancelCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun cancelButton_clicked_callsCorrectCallback() {
+ underTest.onCreate(dialog, null)
+
+ dialog.requireViewById<View>(R.id.cancel).callOnClick()
+
+ verify(onCancelCallback).onClick(any())
+ verify(onStartMirroringCallback, never()).onClick(any())
+ }
+
+ @Test
+ fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
+ underTest.onCreate(dialog, null)
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ underTest.onStop(dialog)
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
+ underTest.onCreate(dialog, null)
+ dialog.requireViewById<View>(R.id.enable_display).callOnClick()
+
+ underTest.onStop(dialog)
+
+ verify(onCancelCallback, never()).onClick(any())
+ verify(onStartMirroringCallback).onClick(any())
+ }
+
+ @Test
+ fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+ underTest.onCreate(dialog, null)
+ underTest.onStart(dialog)
+
+ val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+
+ triggerInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ @Test
+ fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+ underTest.onCreate(dialog, null)
+ underTest.onStart(dialog)
+
+ val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+ triggerInsetsChanged(WindowInsets.Type.ime(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isNotEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+ return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+ }
+
+ private fun triggerInsetsChanged(type: Int, insets: WindowInsets) {
+ verify(windowDecorView)
+ .setWindowInsetsAnimationCallback(capture(windowInsetsAnimationCallbackCaptor))
+ windowInsetsAnimationCallbackCaptor.value.onProgress(
+ insets,
+ listOf(WindowInsetsAnimation(type, Interpolators.INSTANT, 0))
+ )
+ }
+
+ private companion object {
+ const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
deleted file mode 100644
index 30519b0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.display.ui.view
-
-import android.graphics.Insets
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.View
-import android.view.WindowInsets
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.res.R
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MirroringConfirmationDialogTest : SysuiTestCase() {
-
- private lateinit var dialog: MirroringConfirmationDialog
-
- private val onStartMirroringCallback = mock<View.OnClickListener>()
- private val onCancelCallback = mock<View.OnClickListener>()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- dialog =
- MirroringConfirmationDialog(
- context,
- onStartMirroringCallback,
- onCancelCallback,
- navbarBottomInsetsProvider = { 0 },
- )
- }
-
- @Test
- fun startMirroringButton_clicked_callsCorrectCallback() {
- dialog.show()
-
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- verify(onStartMirroringCallback).onClick(any())
- verify(onCancelCallback, never()).onClick(any())
- }
-
- @Test
- fun cancelButton_clicked_callsCorrectCallback() {
- dialog.show()
-
- dialog.requireViewById<View>(R.id.cancel).callOnClick()
-
- verify(onCancelCallback).onClick(any())
- verify(onStartMirroringCallback, never()).onClick(any())
- }
-
- @Test
- fun onCancel_afterEnablingMirroring_cancelCallbackNotCalled() {
- dialog.show()
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- dialog.cancel()
-
- verify(onCancelCallback, never()).onClick(any())
- verify(onStartMirroringCallback).onClick(any())
- }
-
- @Test
- fun onDismiss_afterEnablingMirroring_cancelCallbackNotCalled() {
- dialog.show()
- dialog.requireViewById<View>(R.id.enable_display).callOnClick()
-
- dialog.dismiss()
-
- verify(onCancelCallback, never()).onClick(any())
- verify(onStartMirroringCallback).onClick(any())
- }
-
- @Test
- fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
- dialog.show()
-
- val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
- dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
-
- assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
- .isEqualTo(TEST_BOTTOM_INSETS)
- }
-
- @Test
- fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
- dialog.show()
-
- val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
- dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
-
- assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
- .isNotEqualTo(TEST_BOTTOM_INSETS)
- }
-
- private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
- return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
- }
-
- @After
- fun teardown() {
- if (::dialog.isInitialized) {
- dialog.dismiss()
- }
- }
-
- private companion object {
- const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 318227f..709f779 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,8 +25,8 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
+import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -102,7 +102,6 @@
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -222,7 +221,6 @@
private @Mock DreamViewModel mDreamViewModel;
private @Mock CommunalTransitionViewModel mCommunalTransitionViewModel;
private @Mock SystemPropertiesHelper mSystemPropertiesHelper;
- private @Mock SceneContainerFlags mSceneContainerFlags;
private FakeFeatureFlags mFeatureFlags;
private final int mDefaultUserId = 100;
@@ -270,7 +268,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
@@ -1243,7 +1240,7 @@
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
- mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
+ mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
}
private void captureKeyguardStateControllerCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index b0aace6..b50d248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -63,8 +62,8 @@
val withDeps =
KeyguardInteractorFactory.create(
- repository = keyguardRepository,
featureFlags = featureFlags,
+ repository = keyguardRepository,
)
val keyguardInteractor = withDeps.keyguardInteractor
resourceTrimmer =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 9266af4..dc7f372 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -40,7 +41,6 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -87,7 +87,6 @@
@Before
fun setup() {
mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
- kosmos.fakeSceneContainerFlags.enabled = false
primaryBouncerInteractor =
PrimaryBouncerInteractor(
@@ -127,7 +126,6 @@
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -168,15 +166,14 @@
}
@Test
+ @EnableSceneContainer
fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
@@ -196,15 +193,14 @@
}
@Test
+ @EnableSceneContainer
fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() =
testScope.runTest {
- kosmos.fakeSceneContainerFlags.enabled = true
underTest =
DeviceEntrySideFpsOverlayInteractor(
testScope.backgroundScope,
mContext,
deviceEntryFingerprintAuthRepository,
- kosmos.fakeSceneContainerFlags,
kosmos.sceneInteractor,
primaryBouncerInteractor,
alternateBouncerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
index 3f05bfa..9ccf212 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -19,6 +19,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -227,4 +228,50 @@
{ it == KeyguardSurfaceBehindModel(alpha = 0f) },
)
}
+
+ @Test
+ fun notificationLaunchFromLockscreen_isAnimatingSurfaceTrue() =
+ testScope.runTest {
+ val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+ assertThat(isAnimatingSurface).isTrue()
+ }
+
+ @Test
+ fun notificationLaunchFromGone_isAnimatingSurfaceFalse() =
+ testScope.runTest {
+ val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ transitionState = TransitionState.FINISHED,
+ )
+ )
+ kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(true)
+ runCurrent()
+ assertThat(isAnimatingSurface).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index 66aa572..5e3a142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -113,7 +113,8 @@
id = "WEATHER_CLOCK",
name = "",
description = "",
- useAlternateSmartspaceAODTransition = true
+ useAlternateSmartspaceAODTransition = true,
+ useCustomClockScene = true
)
whenever(clock.config).thenReturn(clockConfig)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index fe8fdc0..8f73811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -50,6 +50,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
@@ -76,9 +77,10 @@
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {
+ @Mock private lateinit var listener: MediaDataProcessor.Listener
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
- @Mock private lateinit var mediaDataManager: MediaDataManager
+ @Mock private lateinit var mediaDataProcessor: MediaDataProcessor
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var executor: Executor
@Mock private lateinit var smartspaceData: SmartspaceMediaData
@@ -101,7 +103,7 @@
MediaPlayerData.clear()
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
testScope = TestScope()
- repository = MediaFilterRepository()
+ repository = MediaFilterRepository(FakeSystemClock())
mediaDataFilter =
MediaDataFilterImpl(
context,
@@ -114,7 +116,8 @@
mediaFlags,
repository,
)
- mediaDataFilter.mediaDataManager = mediaDataManager
+ mediaDataFilter.mediaDataProcessor = mediaDataProcessor
+ mediaDataFilter.addListener(listener)
// Start all tests as main user
setUser(USER_MAIN)
@@ -167,6 +170,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
}
@@ -178,6 +183,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
}
@@ -196,6 +203,7 @@
mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
}
@@ -208,6 +216,7 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
mediaDataFilter.onMediaDataRemoved(KEY)
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -226,6 +235,7 @@
setUser(USER_GUEST)
// THEN we should remove the main user's media
+ verify(listener).onMediaDataRemoved(eq(KEY))
assertThat(mediaDataLoadedStates).isEmpty()
}
@@ -243,6 +253,20 @@
// and we switch to guest user
setUser(USER_GUEST)
+ // THEN we should add back the guest user media
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest), eq(true), eq(0), eq(false))
+
+ // but not the main user's
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ any(),
+ eq(dataMain),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean()
+ )
assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
}
@@ -261,6 +285,7 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
// THEN we should remove the private profile media
+ verify(listener).onMediaDataRemoved(eq(KEY_ALT))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
}
@@ -481,7 +506,7 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
mediaDataFilter.onSwipeToDismiss()
- verify(mediaDataManager).setInactive(eq(KEY), eq(true), eq(true))
+ verify(mediaDataProcessor).setInactive(eq(KEY), eq(true), eq(true))
}
@Test
@@ -507,6 +532,8 @@
)
.isTrue()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -534,6 +561,9 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -563,6 +593,8 @@
)
.isTrue()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(true))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -592,6 +624,7 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -614,6 +647,8 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -629,6 +664,9 @@
)
.isFalse()
assertThat(hasActiveMedia(selectedUserEntries)).isFalse()
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), eq(KEY), any(), anyBoolean(), anyInt(), anyBoolean())
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger, never()).logRecommendationActivated(any(), any(), any())
}
@@ -649,12 +687,15 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -664,8 +705,18 @@
)
)
.isTrue()
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
// Smartspace update shouldn't be propagated for the empty rec list.
assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
+ verify(listener, never()).onSmartspaceMediaDataLoaded(any(), any(), anyBoolean())
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@@ -687,12 +738,24 @@
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -704,6 +767,8 @@
.isTrue()
// Smartspace update should also be propagated but not prioritized.
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@@ -721,6 +786,7 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -746,15 +812,29 @@
val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -781,6 +861,8 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -813,12 +895,18 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// And an inactive recommendation is loaded
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// Smartspace is loaded but the media stays inactive
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
+ verify(listener, never())
+ .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean())
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -846,8 +934,8 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, data)
mediaDataFilter.onSwipeToDismiss()
- verify(mediaDataManager).setRecommendationInactive(eq(SMARTSPACE_KEY))
- verify(mediaDataManager, never())
+ verify(mediaDataProcessor).setRecommendationInactive(eq(SMARTSPACE_KEY))
+ verify(mediaDataProcessor, never())
.dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong())
}
@@ -866,6 +954,8 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to trigger resume
@@ -875,6 +965,16 @@
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// THEN we should treat the media as active instead
+ val dataCurrentAndActive = dataCurrent.copy(active = true)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(KEY),
+ eq(dataCurrentAndActive),
+ eq(true),
+ eq(100),
+ eq(true)
+ )
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
@@ -886,6 +986,8 @@
.isTrue()
// And update the smartspace data state, but not prioritized
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
}
@Test
@@ -901,6 +1003,8 @@
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
+ verify(listener)
+ .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false))
assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to not trigger resume
@@ -908,7 +1012,12 @@
whenever(cardAction.extras).thenReturn(extras)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ // THEN listeners are not updated to show media
+ verify(listener, never())
+ .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true))
// But the smartspace update is still propagated
+ verify(listener)
+ .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false))
assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 5c275b4..ffb50c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -210,7 +210,7 @@
)
testDispatcher = UnconfinedTestDispatcher()
testScope = TestScope(testDispatcher)
- mediaFilterRepository = MediaFilterRepository()
+ mediaFilterRepository = MediaFilterRepository(clock)
mediaDataRepository = MediaDataRepository(mediaFlags, dumpManager)
mediaDataProcessor =
MediaDataProcessor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index a73bb2c..e5d3082 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -16,29 +16,54 @@
package com.android.systemui.media.controls.ui.controller
+import android.animation.AnimatorSet
+import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.RippleDrawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Interpolator
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.SeekBar
+import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.internal.widget.CachingIconView
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
+import com.android.systemui.surfaceeffects.ripple.MultiRippleView
+import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.animation.TransitionViewState
import com.android.systemui.util.animation.WidgetState
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -55,6 +80,31 @@
com.android.systemui.statusbar.phone.ConfigurationControllerImpl(context)
private var player = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
private var recommendation = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0)
+ private val clock = FakeSystemClock()
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var seekBar: SeekBar
+ private lateinit var multiRippleView: MultiRippleView
+ private lateinit var turbulenceNoiseView: TurbulenceNoiseView
+ private lateinit var loadingEffectView: LoadingEffectView
+ private lateinit var settings: ImageButton
+ private lateinit var cancel: View
+ private lateinit var cancelText: TextView
+ private lateinit var dismiss: FrameLayout
+ private lateinit var dismissText: TextView
+ private lateinit var titleText: TextView
+ private lateinit var artistText: TextView
+ private lateinit var explicitIndicator: CachingIconView
+ private lateinit var seamless: ViewGroup
+ private lateinit var seamlessButton: View
+ private lateinit var seamlessIcon: ImageView
+ private lateinit var seamlessText: TextView
+ private lateinit var scrubbingElapsedTimeView: TextView
+ private lateinit var scrubbingTotalTimeView: TextView
+ private lateinit var actionPlayPause: ImageButton
+ private lateinit var actionNext: ImageButton
+ private lateinit var actionPrev: ImageButton
+ @Mock private lateinit var seamlessBackground: RippleDrawable
+ @Mock private lateinit var albumView: ImageView
@Mock lateinit var logger: MediaViewLogger
@Mock private lateinit var mockViewState: TransitionViewState
@Mock private lateinit var mockCopiedState: TransitionViewState
@@ -64,6 +114,14 @@
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var seekBarViewModel: SeekBarViewModel
+ @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress>
+ @Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var viewHolder: MediaViewHolder
+ @Mock private lateinit var view: TransitionLayout
+ @Mock private lateinit var mockAnimator: AnimatorSet
+ @Mock private lateinit var gutsViewHolder: GutsViewHolder
+ @Mock private lateinit var gutsText: TextView
private val delta = 0.1F
@@ -72,14 +130,30 @@
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ mainExecutor = FakeExecutor(clock)
mediaViewController =
- MediaViewController(
- context,
- configurationController,
- mediaHostStatesManager,
- logger,
- mediaFlags,
- )
+ object :
+ MediaViewController(
+ context,
+ configurationController,
+ mediaHostStatesManager,
+ logger,
+ seekBarViewModel,
+ mainExecutor,
+ mediaFlags,
+ globalSettings,
+ ) {
+ override fun loadAnimator(
+ context: Context,
+ animId: Int,
+ motionInterpolator: Interpolator?,
+ vararg targets: View?
+ ): AnimatorSet {
+ return mockAnimator
+ }
+ }
+ initGutsViewHolderMocks()
+ initMediaViewHolderMocks()
}
@Test
@@ -299,4 +373,270 @@
verify(mediaTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
verify(mediaSubTitleWidgetState).alpha = floatThat { kotlin.math.abs(it - 1.0F) < delta }
}
+
+ @Test
+ fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+ getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_seekBarEnabled_seekBarVisible() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ getEnabledChangeListener().onEnabledChanged(enabled = true)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.VISIBLE)
+
+ getEnabledChangeListener().onEnabledChanged(enabled = false)
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.media_progress_bar))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ }
+
+ @Test
+ fun attachPlayer_notScrubbing_scrubbingViewsGone() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.canShowScrubbingTime = true
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ getScrubbingChangeListener().onScrubbingChanged(false)
+ mainExecutor.runAllReady()
+
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.canShowScrubbingTime = false
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true)
+ mediaViewController.setUpPrevButtonInfo(false)
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(false)
+ mediaViewController.setUpPrevButtonInfo(true)
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ @Test
+ fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true)
+ mediaViewController.setUpPrevButtonInfo(true)
+ mediaViewController.canShowScrubbingTime = true
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ // Only in expanded, we should show the scrubbing times and hide prev+next
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ }
+
+ @Test
+ fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
+ whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+
+ mediaViewController.attachPlayer(viewHolder)
+ mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
+ mediaViewController.setUpPrevButtonInfo(true, ConstraintSet.INVISIBLE)
+ mediaViewController.canShowScrubbingTime = true
+
+ getScrubbingChangeListener().onScrubbingChanged(true)
+ mainExecutor.runAllReady()
+
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.INVISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.VISIBLE)
+
+ getScrubbingChangeListener().onScrubbingChanged(false)
+ mainExecutor.runAllReady()
+
+ // Only in expanded, we should hide the scrubbing times and show prev+next
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionPrev))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(mediaViewController.expandedLayout.getVisibility(R.id.actionNext))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_elapsed_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ assertThat(
+ mediaViewController.expandedLayout.getVisibility(R.id.media_scrubbing_total_time)
+ )
+ .isEqualTo(ConstraintSet.GONE)
+ }
+
+ private fun initGutsViewHolderMocks() {
+ settings = ImageButton(context)
+ cancel = View(context)
+ cancelText = TextView(context)
+ dismiss = FrameLayout(context)
+ dismissText = TextView(context)
+ whenever(gutsViewHolder.gutsText).thenReturn(gutsText)
+ whenever(gutsViewHolder.settings).thenReturn(settings)
+ whenever(gutsViewHolder.cancel).thenReturn(cancel)
+ whenever(gutsViewHolder.cancelText).thenReturn(cancelText)
+ whenever(gutsViewHolder.dismiss).thenReturn(dismiss)
+ whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
+ }
+
+ private fun initMediaViewHolderMocks() {
+ titleText = TextView(context)
+ artistText = TextView(context)
+ explicitIndicator = CachingIconView(context).also { it.id = R.id.media_explicit_indicator }
+ seamless = FrameLayout(context)
+ seamless.foreground = seamlessBackground
+ seamlessButton = View(context)
+ seamlessIcon = ImageView(context)
+ seamlessText = TextView(context)
+ seekBar = SeekBar(context).also { it.id = R.id.media_progress_bar }
+
+ actionPlayPause = ImageButton(context).also { it.id = R.id.actionPlayPause }
+ actionPrev = ImageButton(context).also { it.id = R.id.actionPrev }
+ actionNext = ImageButton(context).also { it.id = R.id.actionNext }
+ scrubbingElapsedTimeView =
+ TextView(context).also { it.id = R.id.media_scrubbing_elapsed_time }
+ scrubbingTotalTimeView = TextView(context).also { it.id = R.id.media_scrubbing_total_time }
+
+ multiRippleView = MultiRippleView(context, null)
+ turbulenceNoiseView = TurbulenceNoiseView(context, null)
+ loadingEffectView = LoadingEffectView(context, null)
+
+ whenever(viewHolder.player).thenReturn(view)
+ whenever(view.context).thenReturn(context)
+ whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(albumView.foreground).thenReturn(Mockito.mock(Drawable::class.java))
+ whenever(viewHolder.titleText).thenReturn(titleText)
+ whenever(viewHolder.artistText).thenReturn(artistText)
+ whenever(viewHolder.explicitIndicator).thenReturn(explicitIndicator)
+ whenever(seamlessBackground.getDrawable(0))
+ .thenReturn(Mockito.mock(GradientDrawable::class.java))
+ whenever(viewHolder.seamless).thenReturn(seamless)
+ whenever(viewHolder.seamlessButton).thenReturn(seamlessButton)
+ whenever(viewHolder.seamlessIcon).thenReturn(seamlessIcon)
+ whenever(viewHolder.seamlessText).thenReturn(seamlessText)
+ whenever(viewHolder.seekBar).thenReturn(seekBar)
+ whenever(viewHolder.scrubbingElapsedTimeView).thenReturn(scrubbingElapsedTimeView)
+ whenever(viewHolder.scrubbingTotalTimeView).thenReturn(scrubbingTotalTimeView)
+ whenever(viewHolder.gutsViewHolder).thenReturn(gutsViewHolder)
+ whenever(seekBarViewModel.progress).thenReturn(seekBarData)
+
+ // Action buttons
+ whenever(viewHolder.actionPlayPause).thenReturn(actionPlayPause)
+ whenever(viewHolder.getAction(R.id.actionNext)).thenReturn(actionNext)
+ whenever(viewHolder.getAction(R.id.actionPrev)).thenReturn(actionPrev)
+ whenever(viewHolder.getAction(R.id.actionPlayPause)).thenReturn(actionPlayPause)
+
+ whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
+ whenever(viewHolder.turbulenceNoiseView).thenReturn(turbulenceNoiseView)
+ whenever(viewHolder.loadingEffectView).thenReturn(loadingEffectView)
+ }
+
+ private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
+ withArgCaptor {
+ verify(seekBarViewModel).setScrubbingChangeListener(capture())
+ }
+
+ private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor {
+ verify(seekBarViewModel).setEnabledChangeListener(capture())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..9bb21f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -123,11 +123,22 @@
mMediaControllers.add(mMediaController);
when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
// Using a fake package will cause routing operations to fail, so we intercept
// scanning-related operations.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index c9eb67e..2e6388a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -124,11 +124,22 @@
when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
mBroadcastSender, mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 980eb59..4eb0038 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -194,11 +194,22 @@
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
mCachedBluetoothDeviceManager);
- mMediaOutputController = new MediaOutputController(mSpyContext, mPackageName,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ mPackageName,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -276,11 +287,22 @@
@Test
public void start_withoutPackageName_verifyMediaControllerInit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.start(mCb);
@@ -306,11 +328,22 @@
@Test
public void stop_withoutPackageName_verifyMediaControllerDeinit() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.start(mCb);
@@ -550,12 +583,22 @@
@Test
public void getAppSourceName_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- "",
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ "",
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -573,12 +616,22 @@
@Test
public void getNotificationSmallIcon_packageNameIsNull_returnsNull() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- "",
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ "",
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -609,12 +662,22 @@
@Test
public void addDeviceToPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -625,12 +688,22 @@
@Test
public void removeDeviceFromPlayMedia_callsLocalMediaManager() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
LocalMediaManager mockLocalMediaManager = mock(LocalMediaManager.class);
testMediaOutputController.mLocalMediaManager = mockLocalMediaManager;
@@ -894,11 +967,22 @@
@Test
public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
- mMediaOutputController = new MediaOutputController(mSpyContext, null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -1085,12 +1169,22 @@
@Test
public void setTemporaryAllowListExceptionIfNeeded_packageNameIsNull_NoAction() {
- MediaOutputController testMediaOutputController = new MediaOutputController(mSpyContext,
- null,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ MediaOutputController testMediaOutputController =
+ new MediaOutputController(
+ mSpyContext,
+ null,
+ mSpyContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 83def8e4..3b6a88a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -18,6 +18,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -61,7 +62,7 @@
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
verify(mMockMediaOutputDialogManager, times(1))
- .createAndShow(getContext().getPackageName(), false, null);
+ .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -72,7 +73,8 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -82,7 +84,8 @@
Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -95,7 +98,8 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -108,9 +112,10 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, times(1))
- .createAndShow(getContext().getPackageName(), true, null);
+ .createAndShow(eq(getContext().getPackageName()), eq(true), any());
}
@Test
@@ -121,7 +126,8 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -133,7 +139,8 @@
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -145,7 +152,8 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
@@ -155,7 +163,8 @@
Intent intent = new Intent("UnKnown Action");
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any(), any());
verify(mMockMediaOutputBroadcastDialogManager, never())
.createAndShow(any(), anyBoolean(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 84300da..cdef964 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -137,11 +137,22 @@
Mockito.eq(userHandle))).thenReturn(
mMediaControllers);
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotifCollection, mDialogTransitionAnimator,
- mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags, mUserTracker);
+ mMediaOutputController =
+ new MediaOutputController(
+ mContext,
+ TEST_PACKAGE,
+ mContext.getUser(),
+ mMediaSessionManager,
+ mLocalBluetoothManager,
+ mStarter,
+ mNotifCollection,
+ mDialogTransitionAnimator,
+ mNearbyMediaDevicesManager,
+ mAudioManager,
+ mPowerExemptionManager,
+ mKeyguardManager,
+ mFlags,
+ mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = makeTestDialog(mMediaOutputController);
mMediaOutputDialog.show();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index a702dda..224e755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -35,6 +35,7 @@
import static org.mockito.Mockito.when;
import android.content.ComponentName;
+import android.content.res.Configuration;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -55,6 +56,8 @@
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import dagger.Lazy;
@@ -117,6 +120,7 @@
EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory;
@Mock
NotificationShadeWindowController mNotificationShadeWindowController;
+ ConfigurationController mConfigurationController = new FakeConfigurationController();
private AccessibilityManager.AccessibilityServicesStateChangeListener
mAccessibilityServicesStateChangeListener;
@@ -144,9 +148,8 @@
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
- mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
- mSynchronousExecutor);
-
+ mDisplayTracker, mNotificationShadeWindowController, mConfigurationController,
+ mDumpManager, mCommandQueue, mSynchronousExecutor);
}
@Test
@@ -335,6 +338,12 @@
assertThat(state2.mWindowState).isNotEqualTo(newState);
}
+ @Test
+ public void configUpdatePropagatesToEdgeBackGestureHandler() {
+ mConfigurationController.onConfigurationChanged(Configuration.EMPTY);
+ verify(mEdgeBackGestureHandler, times(1)).onConfigurationChanged(any());
+ }
+
private List<String> createFakeShortcutTargets() {
return new ArrayList<>(List.of("a", "b", "c", "d"));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index d405df7..354a87a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -37,11 +37,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.util.SparseArray;
@@ -293,23 +290,6 @@
}
@Test
- public void testConfigurationChange_taskbarNotInitialized() {
- Configuration configuration = mContext.getResources().getConfiguration();
- mNavigationBarController.mIsLargeScreen = true;
- mNavigationBarController.onConfigChanged(configuration);
- verify(mTaskbarDelegate, never()).onConfigurationChanged(configuration);
- }
-
- @Test
- public void testConfigurationChange_taskbarInitialized() {
- Configuration configuration = mContext.getResources().getConfiguration();
- mNavigationBarController.mIsLargeScreen = true;
- when(mTaskbarDelegate.isInitialized()).thenReturn(true);
- mNavigationBarController.onConfigChanged(configuration);
- verify(mTaskbarDelegate, times(1)).onConfigurationChanged(configuration);
- }
-
- @Test
public void testShouldRenderTaskbar_taskbarNotRenderedOnPhone() {
mNavigationBarController.mIsLargeScreen = false;
mNavigationBarController.mIsPhone = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index b38d5e3..0e7a215 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -111,6 +111,7 @@
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxyFake;
@@ -275,8 +276,8 @@
mKeyguardStateController, mock(NavigationModeController.class),
mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
mock(UserTracker.class), mock(DisplayTracker.class),
- mNotificationShadeWindowController, mock(DumpManager.class),
- mock(CommandQueue.class), mSynchronousExecutor));
+ mNotificationShadeWindowController, mock(ConfigurationController.class),
+ mock(DumpManager.class), mock(CommandQueue.class), mSynchronousExecutor));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index e4a4836..6956bea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -57,6 +57,7 @@
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
@@ -66,7 +67,6 @@
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
@@ -115,7 +115,6 @@
@Mock private FooterActionsViewBinder mFooterActionsViewBinder;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@Mock private FeatureFlagsClassic mFeatureFlags;
- @Mock private SceneContainerFlags mSceneContainerFlags;
private ViewGroup mQsView;
private final CommandQueue mCommandQueue =
@@ -127,7 +126,6 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- when(mSceneContainerFlags.isEnabled()).thenReturn(false);
mUnderTest = instantiate();
@@ -496,8 +494,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
clearInvocations(
mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
QSImpl other = instantiate();
@@ -513,9 +511,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
@@ -524,9 +521,8 @@
}
@Test
+ @EnableSceneContainer
public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
- when(mSceneContainerFlags.isEnabled()).thenReturn(true);
-
mUnderTest.onStateChanged(KEYGUARD);
assertThat(mUnderTest.isKeyguardState()).isFalse();
@@ -559,8 +555,8 @@
mFooterActionsViewModelFactory,
mFooterActionsViewBinder,
mLargeScreenShadeInterpolator,
- mFeatureFlags,
- mSceneContainerFlags);
+ mFeatureFlags
+ );
}
private void setUpOther() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index a60494f..0275643 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -17,13 +17,13 @@
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.brightness.BrightnessController
import com.android.systemui.settings.brightness.BrightnessSliderController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
+import javax.inject.Provider
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -36,7 +36,6 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import javax.inject.Provider
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -65,8 +64,6 @@
@Mock private lateinit var pagedTileLayout: PagedTileLayout
@Mock private lateinit var longPressEffectProvider: Provider<QSLongPressEffect>
- private val sceneContainerFlags = FakeSceneContainerFlags()
-
private lateinit var controller: QSPanelController
private val testableResources: TestableResources = mContext.orCreateTestableResources
@@ -103,7 +100,6 @@
falsingManager,
statusBarKeyguardViewManager,
ResourcesSplitShadeStateController(),
- sceneContainerFlags,
longPressEffectProvider,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 8acde36..4915e55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -20,15 +20,14 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -43,8 +42,6 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
- private val sceneContainerFlags = FakeSceneContainerFlags()
-
private lateinit var controller: QuickStatusBarHeaderController
@Before
@@ -55,9 +52,8 @@
`when`(view.context).thenReturn(context)
controller = QuickStatusBarHeaderController(
- view,
- quickQSPanelController,
- sceneContainerFlags,
+ view,
+ quickQSPanelController,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 1313227..387f27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -42,7 +42,6 @@
import com.android.systemui.navigationbar.NavigationBarController
import com.android.systemui.navigationbar.NavigationModeController
import com.android.systemui.recents.OverviewProxyService.ACTION_QUICKSTEP
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeViewController
@@ -249,7 +248,6 @@
sysuiUnlockAnimationController,
inWindowLauncherUnlockAnimationManager,
assistUtils,
- FakeSceneContainerFlags(),
dumpManager,
unfoldTransitionProgressForwarder,
broadcastDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index cf7c6f4..b89ccef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -75,8 +75,6 @@
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -136,7 +134,6 @@
@Mock private ShadeWindowLogger mShadeWindowLogger;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private UserTracker mUserTracker;
- @Mock private SceneContainerFlags mSceneContainerFlags;
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener;
@@ -185,14 +182,12 @@
mKosmos.getDeviceUnlockedInteractor());
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
shadeRepository,
@@ -256,7 +251,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
() -> communalInteractor) {
@Override
protected boolean isDebuggable() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 20d877e..77ad17a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -62,7 +62,6 @@
import com.android.systemui.res.R;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
@@ -208,14 +207,12 @@
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
mShadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 433c95a..6bdd3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -95,7 +94,6 @@
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
- kosmos.sceneContainerFlags,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index 56fc0c7..a05a23b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -21,7 +21,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -34,21 +34,19 @@
@RunWith(AndroidTestingRunner::class)
class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
- @Mock
- private lateinit var parent: ViewGroup
+ @Mock private lateinit var parent: ViewGroup
- @Mock
- private lateinit var shouldBeAnimated: () -> Boolean
+ @Mock private lateinit var shouldBeAnimated: () -> Boolean
private lateinit var animator: UnfoldConstantTranslateAnimator
private val viewsIdToRegister
get() =
setOf(
- ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
- ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
+ ViewIdToTranslate(START_VIEW_ID, Direction.START, shouldBeAnimated),
+ ViewIdToTranslate(END_VIEW_ID, Direction.END, shouldBeAnimated)
)
@Before
@@ -122,11 +120,12 @@
progressProvider.onTransitionStarted()
progressProvider.onTransitionProgress(0f)
- val rtlMultiplier = if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
- 1
- } else {
- -1
- }
+ val rtlMultiplier =
+ if (layoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ 1
+ } else {
+ -1
+ }
list.forEach { (view, direction) ->
assertEquals(
(-MAX_TRANSLATION * direction * rtlMultiplier).toInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index d2fc087..be5af88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -55,7 +55,6 @@
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -87,8 +86,8 @@
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -135,7 +134,6 @@
val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val featureFlags = FakeFeatureFlagsClassic()
val shadeRepository = FakeShadeRepository()
- val sceneContainerFlags = FakeSceneContainerFlags()
val configurationRepository = FakeConfigurationRepository()
val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
@@ -146,7 +144,6 @@
keyguardRepository,
FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(configurationRepository),
shadeRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 54a6523..bb68ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -138,7 +138,6 @@
mHeadsUpManager,
mPowerInteractor,
mActiveNotificationsInteractor,
- mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db053d8..9e2856d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -180,7 +180,6 @@
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
mActiveNotificationsInteractor,
- mKosmos.getSceneContainerFlags(),
() -> mKosmos.getSceneInteractor()
);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 65a960b..1b85dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -45,6 +45,7 @@
import com.android.internal.statusbar.statusBarService
import com.android.systemui.SysuiTestCase
import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -55,7 +56,6 @@
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.shade.shadeControllerSceneImpl
@@ -93,6 +93,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
+@EnableSceneContainer
class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
private val testNotificationChannel =
NotificationChannel(
@@ -143,8 +144,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- val sceneContainerFlags = kosmos.fakeSceneContainerFlags
- sceneContainerFlags.enabled = true
allowTestableLooperAsMainThread()
helper = NotificationTestHelper(mContext, mDependency)
Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
@@ -156,9 +155,9 @@
headsUpManager,
create().powerInteractor,
activeNotificationsInteractor,
- sceneContainerFlags,
- { sceneInteractor },
- )
+ ) {
+ sceneInteractor
+ }
gutsManager =
NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
index 4bfd7e3..df82df8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON
import com.android.systemui.power.shared.model.WakefulnessState.STARTING_TO_SLEEP
import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
import com.android.systemui.util.animation.data.repository.FakeAnimationStatusRepository
@@ -59,7 +59,7 @@
private val animationStatus = FakeAnimationStatusRepository()
private val configurationController = FakeConfigurationController()
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+ private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
private val powerRepository = FakePowerRepository()
private val powerInteractor =
PowerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 25e4728..5c65103 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -101,6 +102,7 @@
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
@@ -120,7 +122,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor;
@@ -336,8 +337,6 @@
private final DumpManager mDumpManager = new DumpManager();
private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
- private final FakeSceneContainerFlags mSceneContainerFlags = new FakeSceneContainerFlags();
-
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
@@ -351,7 +350,9 @@
// Turn AOD on and toggle feature flag for jank fixes
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ if (!com.android.systemui.Flags.sceneContainer()) {
+ mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ }
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
@@ -426,22 +427,25 @@
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
-
- mShadeController = spy(new ShadeControllerImpl(
- mCommandQueue,
- mMainExecutor,
- mock(WindowRootViewVisibilityInteractor.class),
- mKeyguardStateController,
- mStatusBarStateController,
- mStatusBarKeyguardViewManager,
- mStatusBarWindowController,
- mDeviceProvisionedController,
- mNotificationShadeWindowController,
- 0,
- () -> mNotificationPanelViewController,
- () -> mAssistManager,
- () -> mNotificationGutsManager
- ));
+ if (com.android.systemui.Flags.sceneContainer()) {
+ mShadeController = spy(mKosmos.getShadeController());
+ } else {
+ mShadeController = spy(new ShadeControllerImpl(
+ mCommandQueue,
+ mMainExecutor,
+ mock(WindowRootViewVisibilityInteractor.class),
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mStatusBarKeyguardViewManager,
+ mStatusBarWindowController,
+ mDeviceProvisionedController,
+ mNotificationShadeWindowController,
+ 0,
+ () -> mNotificationPanelViewController,
+ () -> mAssistManager,
+ () -> mNotificationGutsManager
+ ));
+ }
mShadeController.setNotificationShadeWindowViewController(
mNotificationShadeWindowViewController);
mShadeController.setNotificationPresenter(mNotificationPresenter);
@@ -562,7 +566,6 @@
mUserTracker,
() -> mFingerprintManager,
mActivityStarter,
- mSceneContainerFlags,
mBrightnessMirrorShowingInteractor
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
@@ -572,8 +575,7 @@
any(NotificationPanelViewController.class),
any(ShadeExpansionStateManager.class),
any(BiometricUnlockController.class),
- any(ViewGroup.class),
- any(KeyguardBypassController.class)))
+ any(ViewGroup.class)))
.thenReturn(mStatusBarKeyguardViewManager);
when(mKeyguardViewMediator.getViewMediatorCallback()).thenReturn(
@@ -1095,25 +1097,24 @@
}
@Test
+ @EnableSceneContainer
public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() {
- mSceneContainerFlags.setEnabled(true);
mCentralSurfaces.registerCallbacks();
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
mTestScope.getTestScheduler().runCurrent();
- verify(mScrimController).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ verify(mScrimController, atLeastOnce()).transitionTo(ScrimState.BRIGHTNESS_MIRROR);
mBrightnessMirrorShowingInteractor.setMirrorShowing(false);
mTestScope.getTestScheduler().runCurrent();
ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class);
// The default is to call the one with the callback argument
- verify(mScrimController).transitionTo(captor.capture(), any());
+ verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any());
assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR);
}
@Test
public void brightnesShowingChanged_flagDisabled_ScrimControllerNotified() {
- mSceneContainerFlags.setEnabled(false);
mCentralSurfaces.registerCallbacks();
mBrightnessMirrorShowingInteractor.setMirrorShowing(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 7362e34..6150253 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -24,6 +24,8 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -72,6 +74,7 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
@Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var devicePostureController: DevicePostureController
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var packageManager: PackageManager
@@ -138,7 +141,8 @@
private fun initKeyguardBypassController() {
keyguardBypassController =
KeyguardBypassController(
- context,
+ context.resources,
+ context.packageManager,
testScope.backgroundScope,
tunerService,
statusBarStateController,
@@ -146,7 +150,8 @@
keyguardStateController,
shadeRepository,
devicePostureController,
- dumpManager
+ keyguardTransitionInteractor,
+ dumpManager,
)
}
@@ -302,4 +307,26 @@
job.cancel()
}
}
+
+ @Test
+ fun canBypass_bypassDisabled() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 2 /* FACE_UNLOCK_BYPASS_NEVER */
+ )
+ initKeyguardBypassController()
+ assertThat(keyguardBypassController.canBypass()).isFalse()
+ }
+
+ @Test
+ fun canBypass_bypassEnabled_alternateBouncerShowing() {
+ context.orCreateTestableResources.addOverride(
+ R.integer.config_face_unlock_bypass_override,
+ 1 /* FACE_UNLOCK_BYPASS_ALWAYS */
+ )
+ initKeyguardBypassController()
+ whenever(keyguardTransitionInteractor.getCurrentState())
+ .thenReturn(KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(keyguardBypassController.canBypass()).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index dc3db4c..a6a4f24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -20,9 +20,9 @@
import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
+import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
import static com.google.common.truth.Truth.assertThat;
@@ -172,7 +172,6 @@
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mKosmos.getSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 1463680..d365663 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,7 +33,6 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
@@ -51,6 +50,8 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import javax.inject.Provider
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -61,8 +62,6 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import javax.inject.Provider
@SmallTest
class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@@ -296,7 +295,6 @@
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
featureFlags,
- FakeSceneContainerFlags(),
userChipViewModel,
centralSurfacesImpl,
statusBarWindowStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index f8c01e7..f04a5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -241,8 +241,7 @@
mShadeLockscreenInteractor,
new ShadeExpansionStateManager(),
mBiometricUnlockController,
- mNotificationContainer,
- mBypassController);
+ mNotificationContainer);
mStatusBarKeyguardViewManager.show(null);
ArgumentCaptor<PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
ArgumentCaptor.forClass(PrimaryBouncerExpansionCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index feff046..1ec1765 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,7 +8,7 @@
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
@@ -23,17 +23,14 @@
@TestableLooper.RunWithLooper
class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var windowManager: WindowManager
+ @Mock private lateinit var windowManager: WindowManager
- @Mock
- private lateinit var display: Display
+ @Mock private lateinit var display: Display
- @Mock
- private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+ @Mock private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
private val view: View = View(context)
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
private lateinit var controller: StatusBarMoveFromCenterAnimationController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
index 1e628bd..dedd0af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -13,68 +13,103 @@
*/
package com.android.systemui.statusbar.phone
+import android.app.Dialog
import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class SystemUIBottomSheetDialogTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
private val configurationController = mock<ConfigurationController>()
private val config = mock<Configuration>()
+ private val delegate = mock<DialogDelegate<Dialog>>()
private lateinit var dialog: SystemUIBottomSheetDialog
@Before
fun setup() {
- dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+ dialog =
+ with(kosmos) {
+ SystemUIBottomSheetDialog(
+ context,
+ testScope.backgroundScope,
+ configurationController,
+ delegate,
+ TestLayout(),
+ 0,
+ )
+ }
}
@Test
fun onStart_registersConfigCallback() {
- dialog.show()
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
- verify(configurationController).addCallback(any())
+ verify(configurationController).addCallback(any())
+ }
}
@Test
fun onStop_unregisterConfigCallback() {
- dialog.show()
- dialog.dismiss()
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
+ dialog.dismiss()
+ runCurrent()
- verify(configurationController).removeCallback(any())
+ verify(configurationController).removeCallback(any())
+ }
}
@Test
- fun onConfigurationChanged_calledInSubclass() {
- var onConfigChangedCalled = false
- val subclass =
- object : SystemUIBottomSheetDialog(mContext, configurationController) {
- override fun onConfigurationChanged() {
- onConfigChangedCalled = true
- }
- }
+ fun onConfigurationChanged_calledInDelegate() {
+ kosmos.testScope.runTest {
+ dialog.show()
+ runCurrent()
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
- subclass.show()
+ captor.value.onConfigChanged(config)
+ runCurrent()
- val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
- verify(configurationController).addCallback(capture(captor))
- captor.value.onConfigChanged(config)
+ verify(delegate).onConfigurationChanged(any(), any())
+ }
+ }
- assertThat(onConfigChangedCalled).isTrue()
+ private class TestLayout : SystemUIBottomSheetDialog.WindowLayout {
+ override fun calculate(): Flow<SystemUIBottomSheetDialog.WindowLayout.Layout> {
+ return flowOf(
+ SystemUIBottomSheetDialog.WindowLayout.Layout(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ )
+ )
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 9b6940e..598b12c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -137,6 +137,7 @@
wifiRepository,
mock(),
mock(),
+ mock(),
)
demoRepo =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 07abd27..b7a3b30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -80,6 +80,7 @@
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
@@ -229,6 +230,7 @@
wifiRepository,
fullConnectionFactory,
updateMonitor,
+ mock(),
)
testScope.runCurrent()
@@ -529,6 +531,7 @@
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -553,6 +556,7 @@
}
@Test
+ @Ignore("b/333912012")
fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -581,6 +585,7 @@
/** Regression test for b/261706421 */
@Test
+ @Ignore("b/333912012")
fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -604,6 +609,54 @@
}
@Test
+ fun testConnectionsCache_keepsReposCached() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Sub1 comes back
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1_1).isSameInstanceAs(repo1_2)
+ }
+
+ @Test
+ fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Client grabs a reference to a repository, but doesn't keep it around
+ underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isNotNull()
+ }
+
+ @Test
fun testConnectionRepository_invalidSubId_doesNotThrow() =
testScope.runTest {
underTest.getRepoForSubId(SUB_1_ID)
@@ -1063,7 +1116,8 @@
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
- updateMonitor
+ updateMonitor,
+ mock(),
)
val latest by collectLastValue(underTest.defaultDataSubRatConfig)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index f9ab25e..dfe8023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -43,15 +43,12 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.seconds
-import kotlin.time.DurationUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -694,32 +691,6 @@
assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
}
- @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
- @Test
- fun satBasedIcon_hasHysteresisWhenDisabled() =
- testScope.runTest {
- val latest by collectLastValue(underTest.signalLevelIcon)
-
- val hysteresisDuration = 5.seconds
- connectionRepository.satelliteConnectionHysteresisSeconds.value =
- hysteresisDuration.toInt(DurationUnit.SECONDS)
-
- connectionRepository.isNonTerrestrial.value = true
-
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Disable satellite
- connectionRepository.isNonTerrestrial.value = false
-
- // Satellite icon should still be visible
- assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java)
-
- // Wait for the icon to change
- advanceTimeBy(hysteresisDuration)
-
- assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java)
- }
-
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index 42bbe3e..c4ab943 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -26,6 +26,10 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.mockito.mock
@@ -53,6 +57,10 @@
private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
private val deviceProvisioningInteractor =
DeviceProvisioningInteractor(deviceProvisionedRepository)
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val wifiRepository = FakeWifiRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
@Before
fun setUp() {
@@ -61,6 +69,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
}
@@ -103,6 +112,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -150,6 +160,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -205,6 +216,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -337,6 +349,7 @@
repo,
iconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -353,4 +366,28 @@
// THEN the value is still false, because the flag is off
assertThat(latest).isFalse()
}
+
+ @Test
+ fun isWifiActive_falseWhenWifiNotActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiActive)
+
+ // WHEN wifi is not active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+ // THEN the interactor returns false due to the wifi network not being active
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun isWifiActive_trueWhenWifiIsActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.isWifiActive)
+
+ // WHEN wifi is active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+ // THEN the interactor returns true due to the wifi network being active
+ assertThat(latest).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index 1d6cd37..64f19b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -26,6 +26,10 @@
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
import com.android.systemui.util.mockito.mock
@@ -44,14 +48,18 @@
private lateinit var underTest: DeviceBasedSatelliteViewModel
private lateinit var interactor: DeviceBasedSatelliteInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
-
private val repo = FakeDeviceBasedSatelliteRepository()
+ private val testScope = TestScope()
+
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
private val deviceProvisionedRepository = FakeDeviceProvisioningRepository()
private val deviceProvisioningInteractor =
DeviceProvisioningInteractor(deviceProvisionedRepository)
-
- private val testScope = TestScope()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val wifiRepository = FakeWifiRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
@Before
fun setUp() {
@@ -63,6 +71,7 @@
repo,
mobileIconsInteractor,
deviceProvisioningInteractor,
+ wifiInteractor,
testScope.backgroundScope,
)
@@ -253,4 +262,40 @@
// THEN icon is null because the device is not provisioned
assertThat(latest).isInstanceOf(Icon::class.java)
}
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun icon_wifiIsActive() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // GIVEN device is provisioned
+ deviceProvisionedRepository.setDeviceProvisioned(true)
+
+ // GIVEN wifi network is active
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 0, level = 1))
+
+ // THEN icon is null because the device is connected to wifi
+ assertThat(latest).isNull()
+
+ // GIVEN device loses wifi connection
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Invalid("test"))
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN icon is set because the device lost wifi connection
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index 69536c5..bdd3d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -30,7 +30,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
@@ -60,7 +59,6 @@
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- kosmos.sceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
index 28adbce..383f4a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -86,7 +86,7 @@
private val areAnimationEnabled = MutableStateFlow(true)
private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
private val systemClock = FakeSystemClock()
- private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+ private val unfoldTransitionProgressProvider = FakeUnfoldTransitionProvider()
private val unfoldTransitionRepository =
UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
private val unfoldTransitionInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index b9c7e61..fd513c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -35,7 +35,7 @@
@SmallTest
class UnfoldHapticsPlayerTest : SysuiTestCase() {
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
private val vibrator: Vibrator = mock()
private val testFoldProvider = TestFoldProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
index ba72716..2955384 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.unfold.util.FoldableDeviceStates
import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,45 +38,41 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.util.Optional
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UnfoldLatencyTrackerTest : SysuiTestCase() {
- @Mock
- lateinit var latencyTracker: LatencyTracker
+ @Mock lateinit var latencyTracker: LatencyTracker
- @Mock
- lateinit var deviceStateManager: DeviceStateManager
+ @Mock lateinit var deviceStateManager: DeviceStateManager
- @Mock
- lateinit var screenLifecycle: ScreenLifecycle
+ @Mock lateinit var screenLifecycle: ScreenLifecycle
- @Captor
- private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+ @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
- @Captor
- private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
+ @Captor private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
private lateinit var deviceStates: FoldableDeviceStates
private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
- private val transitionProgressProvider = TestUnfoldTransitionProvider()
+ private val transitionProgressProvider = FakeUnfoldTransitionProvider()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- unfoldLatencyTracker = UnfoldLatencyTracker(
- latencyTracker,
- deviceStateManager,
- Optional.of(transitionProgressProvider),
- context.mainExecutor,
- context,
- context.contentResolver,
- screenLifecycle
- ).apply { init() }
+ unfoldLatencyTracker =
+ UnfoldLatencyTracker(
+ latencyTracker,
+ deviceStateManager,
+ Optional.of(transitionProgressProvider),
+ context.mainExecutor,
+ context,
+ context.contentResolver,
+ screenLifecycle
+ )
+ .apply { init() }
deviceStates = FoldableTestUtils.findDeviceStates(context)
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
@@ -107,7 +104,7 @@
}
@Test
- fun unfold_firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
+ fun firstFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventNotPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
@@ -118,7 +115,7 @@
}
@Test
- fun unfold_secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun secondFoldEventAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = true)
sendFoldEvent(folded = false)
@@ -131,7 +128,7 @@
}
@Test
- fun unfold_unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
+ fun unfoldFoldUnfoldAnimationsEnabledOnScreenTurnedOnAndTransitionStarted_eventPropagated() {
setAnimationsEnabled(true)
sendFoldEvent(folded = false)
sendFoldEvent(folded = true)
@@ -196,4 +193,4 @@
durationScale.toString()
)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
index 6ec0251..0c452eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt
@@ -17,21 +17,18 @@
@SmallTest
class UnfoldTransitionWallpaperControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var wallpaperController: WallpaperController
+ @Mock private lateinit var wallpaperController: WallpaperController
- private val progressProvider = TestUnfoldTransitionProvider()
+ private val progressProvider = FakeUnfoldTransitionProvider()
- @JvmField
- @Rule
- val mockitoRule = MockitoJUnit.rule()
+ @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
private lateinit var unfoldWallpaperController: UnfoldTransitionWallpaperController
@Before
fun setup() {
- unfoldWallpaperController = UnfoldTransitionWallpaperController(progressProvider,
- wallpaperController)
+ unfoldWallpaperController =
+ UnfoldTransitionWallpaperController(progressProvider, wallpaperController)
unfoldWallpaperController.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 2bc05fc..e5f619b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.utils.os.FakeHandler
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
@@ -34,7 +34,7 @@
@RunWithLooper(setAsMainLooper = true)
class MainThreadUnfoldTransitionProgressProviderTest : SysuiTestCase() {
- private val wrappedProgressProvider = TestUnfoldTransitionProvider()
+ private val wrappedProgressProvider = FakeUnfoldTransitionProvider()
private val fakeHandler = FakeHandler(Looper.getMainLooper())
private val listener = TestUnfoldProgressListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index d864d53..70ec050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -20,7 +20,7 @@
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.updates.RotationChangeProvider
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
@@ -43,14 +43,14 @@
@Mock lateinit var rotationChangeProvider: RotationChangeProvider
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
@Mock lateinit var transitionListener: TransitionProgressListener
@Captor private lateinit var rotationListenerCaptor: ArgumentCaptor<RotationListener>
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- private lateinit var testableLooper : TestableLooper
+ private lateinit var testableLooper: TestableLooper
@Before
fun setUp() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index 2f29b3b..451bd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -22,7 +22,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
import org.junit.Before
@@ -42,7 +42,7 @@
@Mock lateinit var sinkProvider: TransitionProgressListener
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
private lateinit var contentResolver: ContentResolver
private lateinit var progressProvider: ScaleAwareTransitionProgressProvider
@@ -132,6 +132,6 @@
durationScale.toString()
)
- animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+ animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */ false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 95c934e..4486402 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -23,7 +23,7 @@
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.progress.TestUnfoldProgressListener
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -43,7 +43,7 @@
@RunWithLooper
class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
- private val rootProvider = TestUnfoldTransitionProvider()
+ private val rootProvider = FakeUnfoldTransitionProvider()
private val listener = TestUnfoldProgressListener()
private val testScope = TestScope(UnconfinedTestDispatcher())
private val bgThread =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
index f484ea0..cd4d7b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/UnfoldOnlyProgressProviderTest.kt
@@ -19,7 +19,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.FakeUnfoldTransitionProvider
import com.android.systemui.unfold.progress.TestUnfoldProgressListener
import com.google.common.util.concurrent.MoreExecutors
import org.junit.Before
@@ -32,7 +32,7 @@
class UnfoldOnlyProgressProviderTest : SysuiTestCase() {
private val listener = TestUnfoldProgressListener()
- private val sourceProvider = TestUnfoldTransitionProvider()
+ private val sourceProvider = FakeUnfoldTransitionProvider()
private val foldProvider = TestFoldProvider()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
index fb82b8f..483dc0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/UtilTest.java
@@ -15,14 +15,14 @@
*/
package com.android.systemui.volume;
+import static com.google.common.truth.Truth.assertThat;
+
import android.media.MediaMetadata;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import junit.framework.Assert;
-
import org.junit.Test;
@SmallTest
@@ -30,11 +30,59 @@
@Test
public void testMediaMetadataToString_null() {
- Assert.assertEquals(null, Util.mediaMetadataToString(null));
+ assertThat(Util.mediaMetadataToString(null)).isNull();
}
@Test
public void testMediaMetadataToString_notNull() {
- Assert.assertNotNull(Util.mediaMetadataToString(new MediaMetadata.Builder().build()));
+ assertThat(Util.mediaMetadataToString(new MediaMetadata.Builder().build())).isNotNull();
+ }
+
+ @Test
+ public void translateToRange_translatesStartToStart() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 0,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(0);
+ }
+
+ @Test
+ public void translateToRange_translatesValueToValue() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 4,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(400);
+ }
+
+ @Test
+ public void translateToRange_translatesEndToEnd() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 0,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 0,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
+ }
+
+ @Test
+ public void translateToRange_returnsStartForEmptyRange() {
+ assertThat(
+ (int) Util.translateToRange(
+ /* value= */ 7,
+ /* valueRangeStart= */ 7,
+ /* valueRangeEnd= */ 7,
+ /* targetRangeStart= */ 700,
+ /* targetRangeEnd= */700)
+ ).isEqualTo(700);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index ed2fb2c..3b468aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -248,6 +248,8 @@
VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState();
ss.name = STREAMS.get(i);
ss.level = 1;
+ ss.levelMin = 0;
+ ss.levelMax = 25;
state.states.append(i, ss);
}
return state;
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 c24c86c..d9a0c4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -121,8 +121,6 @@
import com.android.systemui.scene.FakeWindowRootViewComponent;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
-import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.shared.logger.SceneLogger;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
@@ -360,8 +358,6 @@
@Mock
private Display mDefaultDisplay;
@Mock
- private SceneContainerFlags mSceneContainerFlags;
- @Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -430,14 +426,12 @@
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
- FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
KeyguardTransitionInteractor keyguardTransitionInteractor =
mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
powerInteractor,
- sceneContainerFlags,
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(configurationRepository),
shadeRepository,
@@ -503,7 +497,6 @@
mShadeWindowLogger,
() -> mSelectedUserInteractor,
mUserTracker,
- mSceneContainerFlags,
mKosmos::getCommunalInteractor
);
mNotificationShadeWindowController.fetchWindowRootView();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
index de7b14d..0682361 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysUITestModule.kt
@@ -31,7 +31,7 @@
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
import com.android.systemui.scene.SceneContainerFrameworkModule
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSource
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
@@ -104,11 +104,10 @@
@Provides
fun provideBaseShadeInteractor(
- sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeInteractorSceneContainerImpl>,
sceneContainerOff: Provider<ShadeInteractorLegacyImpl>
): BaseShadeInteractor {
- return if (sceneContainerFlags.isEnabled()) {
+ return if (SceneContainerFlag.isEnabled) {
sceneContainerOn.get()
} else {
sceneContainerOff.get()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
new file mode 100644
index 0000000..8b0affe2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/FakeReduceBrightColorsController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility
+
+import com.android.systemui.qs.ReduceBrightColorsController
+
+class FakeReduceBrightColorsController : ReduceBrightColorsController {
+
+ private var isEnabled = false
+
+ private val callbacks = LinkedHashSet<ReduceBrightColorsController.Listener>()
+
+ override fun addCallback(listener: ReduceBrightColorsController.Listener) {
+ callbacks.add(listener)
+ }
+
+ override fun removeCallback(listener: ReduceBrightColorsController.Listener) {
+ callbacks.remove(listener)
+ }
+
+ override fun isReduceBrightColorsActivated(): Boolean {
+ return isEnabled
+ }
+
+ override fun setReduceBrightColorsActivated(activated: Boolean) {
+ if (activated != isEnabled) {
+ isEnabled = activated
+ for (callback in callbacks) {
+ callback.onActivated(activated)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.kt
index 979d8e7..5bbe3bf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/ReduceBrightColorsControllerKosmos.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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.accessibility
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.ReduceBrightColorsController
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+var Kosmos.reduceBrightColorsController: ReduceBrightColorsController by
+ Kosmos.Fixture { fakeReduceBrightColorsController }
+val Kosmos.fakeReduceBrightColorsController by Kosmos.Fixture { FakeReduceBrightColorsController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index a6dd3cd..219794f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -32,6 +32,8 @@
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.currentTime
@@ -68,6 +70,8 @@
var lockoutStartedReportCount = 0
+ private val credentialCheckingMutex = Mutex(locked = false)
+
override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
return authenticationMethod.value
}
@@ -124,30 +128,32 @@
override suspend fun checkCredential(
credential: LockscreenCredential
): AuthenticationResultModel {
- val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
- val isSuccessful =
- when {
- credential.type != getCurrentCredentialType(securityMode) -> false
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
- credential.isPin && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
- credential.isPassword && credential.matches(expectedCredential)
- credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
- credential.isPattern && credential.matches(expectedCredential)
- else -> error("Unexpected credential type ${credential.type}!")
- }
+ return credentialCheckingMutex.withLock {
+ val expectedCredential = credentialOverride ?: getExpectedCredential(securityMode)
+ val isSuccessful =
+ when {
+ credential.type != getCurrentCredentialType(securityMode) -> false
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PIN ->
+ credential.isPin && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD ->
+ credential.isPassword && credential.matches(expectedCredential)
+ credential.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN ->
+ credential.isPattern && credential.matches(expectedCredential)
+ else -> error("Unexpected credential type ${credential.type}!")
+ }
- val failedAttempts = _failedAuthenticationAttempts.value
- return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
- AuthenticationResultModel(
- isSuccessful = isSuccessful,
- lockoutDurationMs = 0,
- )
- } else {
- AuthenticationResultModel(
- isSuccessful = false,
- lockoutDurationMs = LOCKOUT_DURATION_MS,
- )
+ val failedAttempts = _failedAuthenticationAttempts.value
+ if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+ AuthenticationResultModel(
+ isSuccessful = isSuccessful,
+ lockoutDurationMs = 0,
+ )
+ } else {
+ AuthenticationResultModel(
+ isSuccessful = false,
+ lockoutDurationMs = LOCKOUT_DURATION_MS,
+ )
+ }
}
}
@@ -155,6 +161,23 @@
_isPinEnhancedPrivacyEnabled.value = isEnabled
}
+ /**
+ * Pauses any future credential checking. The test must call [unpauseCredentialChecking] to
+ * flush the accumulated credential checks.
+ */
+ suspend fun pauseCredentialChecking() {
+ credentialCheckingMutex.lock()
+ }
+
+ /**
+ * Unpauses future credential checking, if it was paused using [pauseCredentialChecking]. This
+ * doesn't flush any pending coroutine jobs; the test code may still choose to do that using
+ * `runCurrent`.
+ */
+ fun unpauseCredentialChecking() {
+ credentialCheckingMutex.unlock()
+ }
+
private fun getExpectedCredential(securityMode: SecurityMode): List<Any> {
return when (val credentialType = getCurrentCredentialType(securityMode)) {
LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index 9ce9ff2..4a02f6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.data.repository.bouncerRepository
import com.android.systemui.classifier.domain.interactor.falsingInteractor
@@ -23,6 +24,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.sessionTracker
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -34,6 +36,8 @@
deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
falsingInteractor = falsingInteractor,
powerInteractor = powerInteractor,
+ uiEventLogger = uiEventLogger,
+ sessionTracker = sessionTracker,
sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
index 5c3e1f4..60d97d1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/ComposeBouncerFlagsKosmos.kt
@@ -17,8 +17,6 @@
package com.android.systemui.bouncer.shared.flag
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-var Kosmos.fakeComposeBouncerFlags by
- Kosmos.Fixture { FakeComposeBouncerFlags(fakeSceneContainerFlags) }
+var Kosmos.fakeComposeBouncerFlags by Kosmos.Fixture { FakeComposeBouncerFlags() }
val Kosmos.composeBouncerFlags by Kosmos.Fixture<ComposeBouncerFlags> { fakeComposeBouncerFlags }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
index c116bbd..7482c0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/shared/flag/FakeComposeBouncerFlags.kt
@@ -16,14 +16,11 @@
package com.android.systemui.bouncer.shared.flag
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
-class FakeComposeBouncerFlags(
- private val sceneContainerFlags: SceneContainerFlags,
- var composeBouncerEnabled: Boolean = false
-) : ComposeBouncerFlags {
+class FakeComposeBouncerFlags(var composeBouncerEnabled: Boolean = false) : ComposeBouncerFlags {
override fun isComposeBouncerOrSceneContainerEnabled(): Boolean {
- return sceneContainerFlags.isEnabled() || composeBouncerEnabled
+ return SceneContainerFlag.isEnabled || composeBouncerEnabled
}
@Deprecated(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 4b6ef37..3dd382f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -34,7 +34,6 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.activityStarter
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.settings.userTracker
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -46,21 +45,20 @@
broadcastDispatcher = broadcastDispatcher,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
- mediaRepository = communalMediaRepository,
communalPrefsRepository = communalPrefsRepository,
+ mediaRepository = communalMediaRepository,
smartspaceRepository = smartspaceRepository,
- appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
+ appWidgetHost = mock(),
editWidgetsActivityStarter = editWidgetsActivityStarter,
userTracker = userTracker,
activityStarter = activityStarter,
userManager = userManager,
dockManager = fakeDockManager,
+ sceneInteractor = sceneInteractor,
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
- communalSettingsInteractor = communalSettingsInteractor,
- sceneInteractor = sceneInteractor,
- sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index e21c766..2e751cc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,12 +24,9 @@
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor.ConfigurationBasedDimensions
@@ -49,7 +46,6 @@
@JvmStatic
fun create(
featureFlags: FakeFeatureFlags = FakeFeatureFlags(),
- sceneContainerFlags: SceneContainerFlags = FakeSceneContainerFlags(),
repository: FakeKeyguardRepository = FakeKeyguardRepository(),
commandQueue: FakeCommandQueue = FakeCommandQueue(),
bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
@@ -88,7 +84,6 @@
repository = repository,
commandQueue = commandQueue,
featureFlags = featureFlags,
- sceneContainerFlags = sceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationRepository = configurationRepository,
shadeRepository = shadeRepository,
@@ -96,13 +91,12 @@
KeyguardInteractor(
repository = repository,
commandQueue = commandQueue,
- sceneContainerFlags = sceneContainerFlags,
+ powerInteractor = powerInteractor,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(configurationRepository),
shadeRepository = shadeRepository,
- sceneInteractorProvider = { sceneInteractor },
keyguardTransitionInteractor = keyguardTransitionInteractor,
- powerInteractor = powerInteractor,
+ sceneInteractorProvider = { sceneInteractor },
fromGoneTransitionInteractor = { fromGoneTransitionInteractor },
sharedNotificationContainerInteractor = { sncInteractor },
applicationScope = testScope,
@@ -114,7 +108,6 @@
val repository: FakeKeyguardRepository,
val commandQueue: FakeCommandQueue,
val featureFlags: FakeFeatureFlags,
- val sceneContainerFlags: SceneContainerFlags,
val bouncerRepository: FakeKeyguardBouncerRepository,
val configurationRepository: FakeConfigurationRepository,
val shadeRepository: FakeShadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index 2a0c01c..9426718 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -34,7 +33,6 @@
repository = keyguardRepository,
commandQueue = commandQueue,
powerInteractor = powerInteractor,
- sceneContainerFlags = sceneContainerFlags,
bouncerRepository = keyguardBouncerRepository,
configurationInteractor = configurationInteractor,
shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 709f864..58b0ff8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -26,7 +26,6 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +46,6 @@
transitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
viewModel = aodToLockscreenTransitionViewModel,
- sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index fdc3e0a..162f278 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -48,10 +48,9 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.sceneContainerConfig
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.shared.model.sceneDataSource
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.statusbar.phone.screenOffAnimationController
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
@@ -71,8 +70,6 @@
val testDispatcher by lazy { kosmos.testDispatcher }
val testScope by lazy { kosmos.testScope }
val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
- val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
- val sceneContainerFlags by lazy { kosmos.sceneContainerFlags }
val fakeExecutor by lazy { kosmos.fakeExecutor }
val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
@@ -112,6 +109,7 @@
}
val brightnessMirrorShowingInteractor by lazy { kosmos.brightnessMirrorShowingInteractor }
val qsLongPressEffect by lazy { kosmos.qsLongPressEffect }
+ val shadeController by lazy { kosmos.shadeController }
init {
kosmos.applicationContext = testCase.context
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
similarity index 67%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.kt
index 979d8e7..eac9149 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/SessionTrackerKosmos.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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.log
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.sessionTracker: SessionTracker by Kosmos.Fixture { sessionTrackerMock }
+val Kosmos.sessionTrackerMock: SessionTracker by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
index 7ce810e..7dab5c2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryKosmos.kt
@@ -17,5 +17,6 @@
package com.android.systemui.media.controls.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.time.systemClock
-val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository() }
+val Kosmos.mediaFilterRepository by Kosmos.Fixture { MediaFilterRepository(systemClock) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
index da2170c..2f3d3c3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui.viewmodel
import android.content.applicationContext
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor
@@ -27,6 +28,7 @@
MediaControlViewModel(
applicationContext = applicationContext,
backgroundDispatcher = testDispatcher,
+ backgroundExecutor = fakeExecutor,
interactor = mediaControlInteractor,
logger = mediaUiEventLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
index 6f652f2..e88d22a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/MediaFlagsKosmos.kt
@@ -18,9 +18,5 @@
import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
-val Kosmos.mediaFlags by
- Kosmos.Fixture {
- MediaFlags(featureFlags = featureFlagsClassic, sceneContainerFlags = sceneContainerFlags)
- }
+val Kosmos.mediaFlags by Kosmos.Fixture { MediaFlags(featureFlags = featureFlagsClassic) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.kt
index 979d8e7..339f3bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/reducebrightness/ReduceBrightColorsTileKosmos.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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.scene.shared.flag
+package com.android.systemui.qs.tiles.impl.reducebrightness
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
-var Kosmos.fakeSceneContainerFlags by Kosmos.Fixture { FakeSceneContainerFlags() }
-val Kosmos.sceneContainerFlags by Kosmos.Fixture<SceneContainerFlags> { fakeSceneContainerFlags }
+val Kosmos.qsReduceBrightColorsTileConfig by
+ Kosmos.Fixture { QSAccessibilityModule.provideReduceBrightColorsTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
deleted file mode 100644
index ded7256..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/flag/FakeSceneContainerFlags.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.flag
-
-import dagger.Binds
-import dagger.Module
-import dagger.Provides
-
-class FakeSceneContainerFlags(
- var enabled: Boolean = SceneContainerFlag.isEnabled,
-) : SceneContainerFlags {
-
- override fun isEnabled(): Boolean {
- return enabled
- }
-
- override fun requirementDescription(): String {
- return ""
- }
-}
-
-@Module(includes = [FakeSceneContainerFlagsModule.Bindings::class])
-class FakeSceneContainerFlagsModule(
- @get:Provides val sceneContainerFlags: FakeSceneContainerFlags = FakeSceneContainerFlags(),
-) {
- @Module
- interface Bindings {
- @Binds fun bindFake(fake: FakeSceneContainerFlags): SceneContainerFlags
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 07e2d6b..543d5b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.ShadeModule
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
@@ -36,7 +35,6 @@
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
Kosmos.Fixture {
ShadeModule.provideBaseShadeInteractor(
- sceneContainerFlags = sceneContainerFlags,
sceneContainerOn = { shadeInteractorSceneContainerImpl },
sceneContainerOff = { shadeInteractorLegacyImpl },
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
new file mode 100644
index 0000000..8d653f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
+import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.mobileIconsViewModel
+
+val Kosmos.shadeHeaderViewModel: ShadeHeaderViewModel by
+ Kosmos.Fixture {
+ ShadeHeaderViewModel(
+ applicationScope = applicationCoroutineScope,
+ context = applicationContext,
+ activityStarter = activityStarter,
+ shadeInteractor = shadeInteractor,
+ mobileIconsInteractor = mobileIconsInteractor,
+ mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = privacyChipInteractor,
+ clockInteractor = shadeHeaderClockInteractor,
+ broadcastDispatcher = broadcastDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index b249211..f0eea38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -21,7 +21,6 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
@@ -30,7 +29,6 @@
dumpManager = dumpManager,
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
- flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
keyguardInteractor = keyguardInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index cf800d0..9c3f510 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -24,7 +24,6 @@
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
@@ -36,7 +35,7 @@
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
- sceneInteractorProvider = { sceneInteractor },
- sceneContainerFlags = sceneContainerFlags,
- )
+ ) {
+ sceneInteractor
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
similarity index 94%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
index 638925d..74b2da4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
new file mode 100644
index 0000000..386c7c5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.airplane.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+
+val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
+ Kosmos.Fixture {
+ AirplaneModeInteractor(
+ FakeAirplaneModeRepository(),
+ FakeConnectivityRepository(),
+ FakeMobileConnectionsRepository(),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 8109b60..eb2d6c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -64,8 +64,6 @@
override val hasPrioritizedNetworkCapabilities = MutableStateFlow(false)
- override val satelliteConnectionHysteresisSeconds = MutableStateFlow(0)
-
private var isInEcmMode: Boolean = false
override suspend fun isInEcmMode(): Boolean = isInEcmMode
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
new file mode 100644
index 0000000..eb6265a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsInteractor: MobileIconsInteractor by
+ Kosmos.Fixture { fakeMobileIconsInteractor }
+val Kosmos.fakeMobileIconsInteractor: FakeMobileIconsInteractor by
+ Kosmos.Fixture { FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
new file mode 100644
index 0000000..c5f6557
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.airplaneModeInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.mobileIconsInteractor
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mobileIconsViewModel: MobileIconsViewModel by
+ Kosmos.Fixture {
+ MobileIconsViewModel(
+ logger = mock(),
+ verboseLogger = mock(),
+ interactor = mobileIconsInteractor,
+ airplaneModeInteractor = airplaneModeInteractor,
+ constants = mock(),
+ flags = featureFlagsClassic,
+ scope = applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 28d632d..331e2fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
index 56c6245..94f0c44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/TestUnfoldTransitionProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/FakeUnfoldTransitionProvider.kt
@@ -2,7 +2,7 @@
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
-class TestUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
+class FakeUnfoldTransitionProvider : UnfoldTransitionProgressProvider, TransitionProgressListener {
private val listeners = mutableListOf<TransitionProgressListener>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
index 7c54a57..a0f5b58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/unfold/UnfoldTransitionProgressProviderKosmos.kt
@@ -18,6 +18,8 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
-var Kosmos.unfoldTransitionProgressProvider by Fixture { mock<UnfoldTransitionProgressProvider>() }
+val Kosmos.fakeUnfoldTransitionProgressProvider by Fixture { FakeUnfoldTransitionProvider() }
+
+val Kosmos.unfoldTransitionProgressProvider by
+ Fixture<UnfoldTransitionProgressProvider> { fakeUnfoldTransitionProgressProvider }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
new file mode 100644
index 0000000..1ef3816
--- /dev/null
+++ b/packages/SystemUI/utils/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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 {
+ default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+ name: "SystemUI-shared-utils",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "kotlin-stdlib",
+ "kotlinx_coroutines",
+ ],
+}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
new file mode 100644
index 0000000..ed97c60
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/FlowConflated.kt
@@ -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.
+ */
+
+@file:OptIn(ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.produceIn
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently.
+ *
+ * The resulting flow is _cold_, which means that [block] is called every time a terminal operator
+ * is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used from any context, e.g. from a callback-based API. The resulting flow completes as
+ * soon as the code in the [block] completes. [awaitClose] should be used to keep the flow running,
+ * otherwise the channel will be closed immediately when block completes. [awaitClose] argument is
+ * called either when a flow consumer cancels the flow collection or when a callback-based API
+ * invokes [SendChannel.close] manually and is typically used to cleanup the resources after the
+ * completion, e.g. unregister a callback. Using [awaitClose] is mandatory in order to prevent
+ * memory leaks when the flow collection is cancelled, otherwise the callback may keep running even
+ * when the flow collector is already completed. To avoid such leaks, this method throws
+ * [IllegalStateException] if block returns, but the channel is not closed yet.
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [callbackFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Example of usage that converts a multi-shot callback API to a flow. For single-shot callbacks use
+ * [suspendCancellableCoroutine].
+ *
+ * ```
+ * fun flowFrom(api: CallbackBasedApi): Flow<T> = callbackFlow {
+ * val callback = object : Callback { // Implementation of some callback interface
+ * override fun onNextValue(value: T) {
+ * // To avoid blocking you can configure channel capacity using
+ * // either buffer(Channel.CONFLATED) or buffer(Channel.UNLIMITED) to avoid overfill
+ * trySendBlocking(value)
+ * .onFailure { throwable ->
+ * // Downstream has been cancelled or failed, can log here
+ * }
+ * }
+ * override fun onApiError(cause: Throwable) {
+ * cancel(CancellationException("API Error", cause))
+ * }
+ * override fun onCompleted() = channel.close()
+ * }
+ * api.register(callback)
+ * /*
+ * * Suspends until either 'onCompleted'/'onApiError' from the callback is invoked
+ * * or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled).
+ * * In both cases, callback will be properly unregistered.
+ * */
+ * awaitClose { api.unregister(callback) }
+ * }
+ * ```
+ * > The callback `register`/`unregister` methods provided by an external API must be thread-safe,
+ * > because `awaitClose` block can be called at any time due to asynchronous nature of
+ * > cancellation, even concurrently with the call of the callback.
+ *
+ * This builder is to be preferred over [callbackFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see callbackFlow
+ */
+fun <T> conflatedCallbackFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = callbackFlow(block).conflate()
+
+/**
+ * Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel] provided to
+ * the builder's [block] of code via [ProducerScope]. It allows elements to be produced by code that
+ * is running in a different context or concurrently. The resulting flow is _cold_, which means that
+ * [block] is called every time a terminal operator is applied to the resulting flow.
+ *
+ * This builder ensures thread-safety and context preservation, thus the provided [ProducerScope]
+ * can be used concurrently from different contexts. The resulting flow completes as soon as the
+ * code in the [block] and all its children completes. Use [awaitClose] as the last statement to
+ * keep it running. A more detailed example is provided in the documentation of [callbackFlow].
+ *
+ * A [conflated][conflate] channel is used. Use the [buffer] operator on the resulting flow to
+ * specify a user-defined value and to control what happens when data is produced faster than
+ * consumed, i.e. to control the back-pressure behavior.
+ *
+ * Adjacent applications of [channelFlow], [flowOn], [buffer], and [produceIn] are always fused so
+ * that only one properly configured channel is used for execution.
+ *
+ * Examples of usage:
+ * ```
+ * fun <T> Flow<T>.merge(other: Flow<T>): Flow<T> = channelFlow {
+ * // collect from one coroutine and send it
+ * launch {
+ * collect { send(it) }
+ * }
+ * // collect and send from this coroutine, too, concurrently
+ * other.collect { send(it) }
+ * }
+ *
+ * fun <T> contextualFlow(): Flow<T> = channelFlow {
+ * // send from one coroutine
+ * launch(Dispatchers.IO) {
+ * send(computeIoValue())
+ * }
+ * // send from another coroutine, concurrently
+ * launch(Dispatchers.Default) {
+ * send(computeCpuValue())
+ * }
+ * }
+ * ```
+ *
+ * This builder is to be preferred over [channelFlow], due to the latter's default configuration of
+ * using an internal buffer, negatively impacting system health.
+ *
+ * @see channelFlow
+ */
+fun <T> conflatedChannelFlow(
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit,
+): Flow<T> = channelFlow(block).conflate()
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
new file mode 100644
index 0000000..5f8c660
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/coroutines/flow/LatestConflated.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalTypeInference::class)
+
+package com.android.systemui.utils.coroutines.flow
+
+import kotlin.experimental.ExperimentalTypeInference
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.FlowCollector
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.transformLatest
+
+/**
+ * Returns a flow that emits elements from the original flow transformed by [transform] function.
+ * When the original flow emits a new value, computation of the [transform] block for previous value
+ * is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.mapLatest { value ->
+ * println("Started computing $value")
+ * delay(200)
+ * "Computed $value"
+ * }
+ * ```
+ *
+ * will print "Started computing a" and "Started computing b", but the resulting flow will contain
+ * only "Computed b" value.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [mapLatest], due to the latter's default configuration of using an internal buffer, negatively
+ * impacting system health.
+ *
+ * @see mapLatest
+ */
+fun <T, R> Flow<T>.mapLatestConflated(@BuilderInference transform: suspend (T) -> R): Flow<R> =
+ mapLatest(transform).conflate()
+
+/**
+ * Returns a flow that switches to a new flow produced by [transform] function every time the
+ * original flow emits a value. When the original flow emits a new value, the previous flow produced
+ * by `transform` block is cancelled.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.flatMapLatest { value ->
+ * flow {
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * }
+ * ```
+ *
+ * produces `a b b_last`
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [flatMapLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see flatMapLatest
+ */
+fun <T, R> Flow<T>.flatMapLatestConflated(
+ @BuilderInference transform: suspend (T) -> Flow<R>,
+): Flow<R> = flatMapLatest(transform).conflate()
+
+/**
+ * Returns a flow that produces element by [transform] function every time the original flow emits a
+ * value. When the original flow emits a new value, the previous `transform` block is cancelled,
+ * thus the name `transformLatest`.
+ *
+ * For example, the following flow:
+ * ```
+ * flow {
+ * emit("a")
+ * delay(100)
+ * emit("b")
+ * }.transformLatest { value ->
+ * emit(value)
+ * delay(200)
+ * emit(value + "_last")
+ * }
+ * ```
+ *
+ * produces `a b b_last`.
+ *
+ * This operator is [conflated][conflate] by default, and as such should be preferred over usage of
+ * [transformLatest], due to the latter's default configuration of using an internal buffer,
+ * negatively impacting system health.
+ *
+ * @see transformLatest
+ */
+fun <T, R> Flow<T>.transformLatestConflated(
+ @BuilderInference transform: suspend FlowCollector<R>.(T) -> Unit,
+): Flow<R> = transformLatest(transform).conflate()
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 23e269a..cbbce1a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -19,6 +19,7 @@
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
@@ -39,6 +40,7 @@
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
@@ -109,22 +111,16 @@
static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
@VisibleForTesting
static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
-
@VisibleForTesting
static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
-
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
-
// Shared preferences constants.
static final String PREFS_NAME = "wbprefs.xml";
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
- /**
- * An approximate area threshold to compare device dimension similarity
- */
- static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+ static final float DEFAULT_ACCEPTABLE_PARALLAX = 0.2f;
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
@@ -336,7 +332,6 @@
mEventLogger.onSystemImageWallpaperBackupFailed(error);
}
-
private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
@@ -409,6 +404,16 @@
}
}
+ private static String readText(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
@VisibleForTesting
// fullBackupFile is final, so we intercept backups here in tests.
protected void backupFile(File file, FullBackupDataOutput data) {
@@ -438,18 +443,10 @@
boolean lockImageStageExists = lockImageStage.exists();
try {
- // Parse the device dimensions of the source device and compare with target to
- // to identify whether we need to skip the remainder of the restore process
+ // Parse the device dimensions of the source device
Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
deviceDimensionsStage);
- Point targetDeviceDimensions = getScreenDimensions();
- if (sourceDeviceDimensions != null && targetDeviceDimensions != null
- && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
- targetDeviceDimensions)) {
- Slog.d(TAG, "The source device is significantly smaller than target");
- }
-
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -466,9 +463,10 @@
// to back up the original image on the source device, or there was no user-supplied
// wallpaper image present.
if (lockImageStageExists) {
- restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
+ restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK,
+ sourceDeviceDimensions);
}
- restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+ restoreFromStage(imageStage, infoStage, "wp", sysWhich, sourceDeviceDimensions);
// And reset to the wallpaper service we should be using
if (mLockHasLiveComponent) {
@@ -543,16 +541,6 @@
}
}
- private static String readText(TypedXmlPullParser parser)
- throws IOException, XmlPullParserException {
- String result = "";
- if (parser.next() == XmlPullParser.TEXT) {
- result = parser.getText();
- parser.nextTag();
- }
- return result;
- }
-
@VisibleForTesting
void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
@@ -578,10 +566,13 @@
}
}
- private void restoreFromStage(File stage, File info, String hintTag, int which)
+ private void restoreFromStage(File stage, File info, String hintTag, int which,
+ Pair<Point, Point> sourceDeviceDimensions)
throws IOException {
if (stage.exists()) {
if (multiCrop()) {
+ // TODO(b/332937943): implement offset adjustment by manually adjusting crop to
+ // adhere to device aspect ratio
SparseArray<Rect> cropHints = parseCropHints(info, hintTag);
if (cropHints != null) {
Slog.i(TAG, "Got restored wallpaper; applying which=" + which
@@ -601,7 +592,6 @@
}
return;
}
-
// Parse the restored info file to find the crop hint. Note that this currently
// relies on a priori knowledge of the wallpaper info file schema.
Rect cropHint = parseCropHint(info, hintTag);
@@ -609,8 +599,33 @@
Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+ "; cropHint = " + cropHint);
try (FileInputStream in = new FileInputStream(stage)) {
- mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
- which);
+
+ if (sourceDeviceDimensions != null && sourceDeviceDimensions.first != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ ParcelFileDescriptor pdf = ParcelFileDescriptor.open(stage, MODE_READ_ONLY);
+ BitmapFactory.decodeFileDescriptor(pdf.getFileDescriptor(),
+ null, options);
+ Point bitmapSize = new Point(options.outWidth, options.outHeight);
+ Point sourceDeviceSize = new Point(sourceDeviceDimensions.first.x,
+ sourceDeviceDimensions.first.y);
+ Point targetDeviceDimensions = getScreenDimensions();
+
+ // TODO: for now we handle only the case where the target device has smaller
+ // aspect ratio than the source device i.e. the target device is more narrow
+ // than the source device
+ if (isTargetMoreNarrowThanSource(targetDeviceDimensions,
+ sourceDeviceSize)) {
+ Rect adjustedCrop = findNewCropfromOldCrop(cropHint,
+ sourceDeviceDimensions.first, true, targetDeviceDimensions,
+ bitmapSize, true);
+
+ cropHint.set(adjustedCrop);
+ }
+ }
+
+ mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint,
+ true, which);
// And log the success
if ((which & FLAG_SYSTEM) > 0) {
@@ -629,6 +644,209 @@
}
}
+ /**
+ * This method computes the crop of the stored wallpaper to preserve its center point as the
+ * user had set it in the previous device.
+ *
+ * The algorithm involves first computing the original crop of the user (without parallax). Then
+ * manually adjusting the user's original crop to respect the current device's aspect ratio
+ * (thereby preserving the center point). Then finally, adding any leftover image real-estate
+ * (i.e. space left over on the horizontal axis) to add parallax effect. Parallax is only added
+ * if was present in the old device's settings.
+ *
+ */
+ private Rect findNewCropfromOldCrop(Rect oldCrop, Point oldDisplaySize, boolean oldRtl,
+ Point newDisplaySize, Point bitmapSize, boolean newRtl) {
+ Rect cropWithoutParallax = withoutParallax(oldCrop, oldDisplaySize, oldRtl, bitmapSize);
+ oldCrop = oldCrop.isEmpty() ? new Rect(0, 0, bitmapSize.x, bitmapSize.y) : oldCrop;
+ float oldParallaxAmount = ((float) oldCrop.width() / cropWithoutParallax.width()) - 1;
+
+ Rect newCropWithSameCenterWithoutParallax = sameCenter(newDisplaySize, bitmapSize,
+ cropWithoutParallax);
+
+ Rect newCrop = newCropWithSameCenterWithoutParallax;
+
+ // calculate the amount of left-over space there is in the image after adjusting the crop
+ // from the above operation i.e. in a rtl configuration, this is the remaining space in the
+ // image after subtracting the new crop's right edge coordinate from the image itself, and
+ // for ltr, its just the new crop's left edge coordinate (as it's the distance from the
+ // beginning of the image)
+ int widthAvailableForParallaxOnTheNewDevice =
+ (newRtl) ? newCrop.left : bitmapSize.x - newCrop.right;
+
+ // calculate relatively how much this available space is as a fraction of the total cropped
+ // image
+ float availableParallaxAmount =
+ (float) widthAvailableForParallaxOnTheNewDevice / newCrop.width();
+
+ float minAcceptableParallax = Math.min(DEFAULT_ACCEPTABLE_PARALLAX, oldParallaxAmount);
+
+ if (DEBUG) {
+ Slog.d(TAG, "- cropWithoutParallax: " + cropWithoutParallax);
+ Slog.d(TAG, "- oldParallaxAmount: " + oldParallaxAmount);
+ Slog.d(TAG, "- newCropWithSameCenterWithoutParallax: "
+ + newCropWithSameCenterWithoutParallax);
+ Slog.d(TAG, "- widthAvailableForParallaxOnTheNewDevice: "
+ + widthAvailableForParallaxOnTheNewDevice);
+ Slog.d(TAG, "- availableParallaxAmount: " + availableParallaxAmount);
+ Slog.d(TAG, "- minAcceptableParallax: " + minAcceptableParallax);
+ Slog.d(TAG, "- oldCrop: " + oldCrop);
+ Slog.d(TAG, "- oldDisplaySize: " + oldDisplaySize);
+ Slog.d(TAG, "- oldRtl: " + oldRtl);
+ Slog.d(TAG, "- newDisplaySize: " + newDisplaySize);
+ Slog.d(TAG, "- bitmapSize: " + bitmapSize);
+ Slog.d(TAG, "- newRtl: " + newRtl);
+ }
+ if (availableParallaxAmount >= minAcceptableParallax) {
+ // but in any case, don't put more parallax than the amount of the old device
+ float parallaxToAdd = Math.min(availableParallaxAmount, oldParallaxAmount);
+
+ int widthToAddForParallax = (int) (newCrop.width() * parallaxToAdd);
+ if (DEBUG) {
+ Slog.d(TAG, "- parallaxToAdd: " + parallaxToAdd);
+ Slog.d(TAG, "- widthToAddForParallax: " + widthToAddForParallax);
+ }
+ if (newRtl) {
+ newCrop.left -= widthToAddForParallax;
+ } else {
+ newCrop.right += widthToAddForParallax;
+ }
+ }
+ return newCrop;
+ }
+
+ /**
+ * This method computes the original crop of the user without parallax.
+ *
+ * NOTE: When the user sets the wallpaper with a specific crop, there may additional image added
+ * to the crop to support parallax. In order to determine the user's actual crop the parallax
+ * must be removed if it exists.
+ */
+ Rect withoutParallax(Rect crop, Point displaySize, boolean rtl, Point bitmapSize) {
+ // in the case an image's crop is not set, we assume the image itself is cropped
+ if (crop.isEmpty()) {
+ crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ }
+
+ if (DEBUG) {
+ Slog.w(TAG, "- crop: " + crop);
+ }
+
+ Rect adjustedCrop = new Rect(crop);
+ float suggestedDisplayRatio = (float) displaySize.x / displaySize.y;
+
+ // here we calculate the width of the wallpaper image such that it has the same aspect ratio
+ // as the given display i.e. the width of the image on a single page of the device without
+ // parallax (i.e. displaySize will correspond to the display the crop was originally set on)
+ int wallpaperWidthWithoutParallax = (int) (0.5f + (float) displaySize.x * crop.height()
+ / displaySize.y);
+ // subtracting wallpaperWidthWithoutParallax from the wallpaper crop gives the amount of
+ // parallax added
+ int widthToRemove = Math.max(0, crop.width() - wallpaperWidthWithoutParallax);
+
+ if (DEBUG) {
+ Slog.d(TAG, "- adjustedCrop: " + adjustedCrop);
+ Slog.d(TAG, "- suggestedDisplayRatio: " + suggestedDisplayRatio);
+ Slog.d(TAG, "- wallpaperWidthWithoutParallax: " + wallpaperWidthWithoutParallax);
+ Slog.d(TAG, "- widthToRemove: " + widthToRemove);
+ }
+ if (rtl) {
+ adjustedCrop.left += widthToRemove;
+ } else {
+ adjustedCrop.right -= widthToRemove;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "- adjustedCrop: " + crop);
+ }
+ return adjustedCrop;
+ }
+
+ /**
+ * This method computes a new crop based on the given crop in order to preserve the center point
+ * of the given crop on the provided displaySize. This is only for the case where the device
+ * displaySize has a smaller aspect ratio than the cropped image.
+ *
+ * NOTE: If the width to height ratio is less in the device display than cropped image
+ * this means the aspect ratios are off and there will be distortions in the image
+ * if the image is applied to the current display (i.e. the image will be skewed ->
+ * pixels in the image will not align correctly with the same pixels in the image that are
+ * above them)
+ */
+ Rect sameCenter(Point displaySize, Point bitmapSize, Rect crop) {
+
+ // in the case an image's crop is not set, we assume the image itself is cropped
+ if (crop.isEmpty()) {
+ crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ }
+
+ float screenRatio = (float) displaySize.x / displaySize.y;
+ float cropRatio = (float) crop.width() / crop.height();
+
+ Rect adjustedCrop = new Rect(crop);
+
+ if (screenRatio < cropRatio) {
+ // the screen is more narrow than the image, and as such, the image will need to be
+ // zoomed in till it fits in the vertical axis. Due to this, we need to manually adjust
+ // the image's crop in order for it to fit into the screen without having the framework
+ // do it (since the framework left aligns the image after zooming)
+
+ // Calculate the height of the adjusted wallpaper crop so it respects the aspect ratio
+ // of the device. To calculate the height, we will use the width of the current crop.
+ // This is so we find the largest height possible which also respects the device aspect
+ // ratio.
+ int heightToAdd = (int) (0.5f + crop.width() / screenRatio - crop.height());
+
+ // Calculate how much extra image space available that can be used to adjust
+ // the crop. If this amount is less than heightToAdd, from above, then that means we
+ // can't use heightToAdd. Instead we will need to use the maximum possible height, which
+ // is the height of the original bitmap. NOTE: the bitmap height may be different than
+ // the crop.
+ // since there is no guarantee to have height available on both sides
+ // (e.g. the available height might be fully at the bottom), grab the minimum
+ int availableHeight = 2 * Math.min(crop.top, bitmapSize.y - crop.bottom);
+ int actualHeightToAdd = Math.min(heightToAdd, availableHeight);
+
+ // half of the additional height is added to the top and bottom of the crop
+ adjustedCrop.top -= actualHeightToAdd / 2 + actualHeightToAdd % 2;
+ adjustedCrop.bottom += actualHeightToAdd / 2;
+
+ // Calculate the width of the adjusted crop. Initially we used the fixed width of the
+ // crop to calculate the heightToAdd, but since this height may be invalid (based on
+ // the calculation above) we calculate the width again instead of using the fixed width,
+ // using the adjustedCrop's updated height.
+ int widthToRemove = (int) (0.5f + crop.width() - adjustedCrop.height() * screenRatio);
+
+ // half of the additional width is subtracted from the left and right side of the crop
+ int widthToRemoveLeft = widthToRemove / 2;
+ int widthToRemoveRight = widthToRemove / 2 + widthToRemove % 2;
+
+ adjustedCrop.left += widthToRemoveLeft;
+ adjustedCrop.right -= widthToRemoveRight;
+
+ if (DEBUG) {
+ Slog.d(TAG, "cropRatio: " + cropRatio);
+ Slog.d(TAG, "screenRatio: " + screenRatio);
+ Slog.d(TAG, "heightToAdd: " + heightToAdd);
+ Slog.d(TAG, "actualHeightToAdd: " + actualHeightToAdd);
+ Slog.d(TAG, "availableHeight: " + availableHeight);
+ Slog.d(TAG, "widthToRemove: " + widthToRemove);
+ Slog.d(TAG, "adjustedCrop: " + adjustedCrop);
+ }
+
+ return adjustedCrop;
+ }
+
+ return adjustedCrop;
+ }
+
+ private boolean isTargetMoreNarrowThanSource(Point targetDisplaySize, Point srcDisplaySize) {
+ float targetScreenRatio = (float) targetDisplaySize.x / targetDisplaySize.y;
+ float srcScreenRatio = (float) srcDisplaySize.x / srcDisplaySize.y;
+
+ return (targetScreenRatio < srcScreenRatio);
+ }
+
private void logRestoreErrorIfNoLiveComponent(int which, String error) {
if (mSystemHasLiveComponent) {
return;
@@ -644,6 +862,7 @@
mEventLogger.onLockImageWallpaperRestoreFailed(error);
}
}
+
private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
Rect cropHint = new Rect();
try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
@@ -681,7 +900,7 @@
if (type != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (!sectionTag.equals(tag)) continue;
- for (Pair<Integer, String> pair: List.of(
+ for (Pair<Integer, String> pair : List.of(
new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
@@ -907,22 +1126,6 @@
return internalDisplays;
}
- /**
- * This method compares the source and target dimensions, and returns true if there is a
- * significant difference in area between them and the source dimensions are smaller than the
- * target dimensions.
- *
- * @param sourceDimensions is the dimensions of the source device
- * @param targetDimensions is the dimensions of the target device
- */
- @VisibleForTesting
- boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
- Point targetDimensions) {
- int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
- - (sourceDimensions.x * sourceDimensions.y);
- return rawAreaDelta > AREA_THRESHOLD;
- }
-
@VisibleForTesting
boolean isDeviceInRestore() {
try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index ec9223c..3ecdf3f 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,7 +59,6 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -841,26 +840,6 @@
testParseCropHints(testMap);
}
- @Test
- public void test_sourceDimensionsAreLargerThanTarget() {
- // source device is larger than target, expecting to get false
- Point sourceDimensions = new Point(2208, 1840);
- Point targetDimensions = new Point(1080, 2092);
- boolean isSourceSmaller = mWallpaperBackupAgent
- .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
- assertThat(isSourceSmaller).isEqualTo(false);
- }
-
- @Test
- public void test_sourceDimensionsMuchSmallerThanTarget() {
- // source device is smaller than target, expecting to get true
- Point sourceDimensions = new Point(1080, 2092);
- Point targetDimensions = new Point(2208, 1840);
- boolean isSourceSmaller = mWallpaperBackupAgent
- .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
- assertThat(isSourceSmaller).isEqualTo(true);
- }
-
private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
assumeTrue(multiCrop());
mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index be2ad21..d279bd5 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -852,11 +852,9 @@
final int pointerIdBits = (1 << pointerId);
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// If we have not delivered the enter schedule an exit.
- if (Flags.resetHoverEventTimerOnActionUp()) {
- // We cancel first to reset the time window so that the user has the full amount of
- // time to do a multi tap.
- mSendHoverEnterAndMoveDelayed.repost();
- }
+ // We cancel first to reset the time window so that the user has the full amount of
+ // time to do a multi tap.
+ mSendHoverEnterAndMoveDelayed.repost();
mSendHoverExitDelayed.post(event, rawEvent, pointerIdBits, policyFlags);
} else {
// The user is touch exploring so we send events for end.
@@ -1601,7 +1599,7 @@
+ " pointers down.");
return;
}
- if (Flags.resetHoverEventTimerOnActionUp() && mEvents.size() == 0) {
+ if (mEvents.size() == 0) {
return;
}
// Send an accessibility event to announce the touch exploration start.
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ced10fb..70ecc05 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -41,3 +41,10 @@
description: "Use weak reference to address binder leak problem"
bug: "307972253"
}
+
+flag {
+ name: "include_last_focused_id_and_session_id_in_client_state"
+ namespace: "autofill"
+ description: "Include the current view id and session id into the FillEventHistory as part of ClientState"
+ bug: "334141398"
+}
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 4506779..ce9d180 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,21 +16,16 @@
package com.android.server.autofill;
-import static com.android.server.autofill.Session.REQUEST_ID_KEY;
-import static com.android.server.autofill.Session.SESSION_ID_KEY;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.os.Bundle;
import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
import android.view.autofill.IAutoFillManagerClient;
-import android.view.inputmethod.InlineSuggestionsRequest;
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -55,6 +50,7 @@
private final RemoteFillService mRemoteFillService;
private final SecondaryProviderCallback mCallback;
+
private int mLastFlag;
SecondaryProviderHandler(
@@ -109,37 +105,18 @@
/**
* Requests a new fill response.
*/
- public void onFillRequest(FillRequest pendingFillRequest,
- InlineSuggestionsRequest pendingInlineSuggestionsRequest, int flag, int id,
+ public void onFillRequest(FillRequest pendingFillRequest, int flag,
IAutoFillManagerClient client) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
Slog.v(TAG, "About to call CredAutofill service as secondary provider");
- FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
- pendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(request, client);
+ mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
} else {
mRemoteFillService.onFillRequest(pendingFillRequest);
}
}
- private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
- InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
- if (pendingFillRequest.getClientState() == null) {
- pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
- pendingFillRequest.getFillContexts(),
- pendingFillRequest.getHints(),
- new Bundle(),
- pendingFillRequest.getFlags(),
- pendingInlineSuggestionsRequest,
- pendingFillRequest.getDelayedFillIntentSender());
- }
- pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
- pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
- return pendingFillRequest;
- }
-
public void destroy() {
mRemoteFillService.destroy();
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0b68f5f..3a38406 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ServiceInfo;
+import android.credentials.CredentialManager;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.graphics.Bitmap;
@@ -252,7 +253,6 @@
private final AutofillManagerServiceImpl mService;
private final Handler mHandler;
private final AutoFillUI mUi;
-
/**
* Context associated with the session, it has the same {@link Context#getDisplayId() displayId}
* of the activity being autofilled.
@@ -751,12 +751,17 @@
if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
&& mSecondaryProviderHandler != null) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
+ if (!mIsPrimaryCredential) {
+ mPendingFillRequest = addCredentialManagerDataToClientState(
+ mPendingFillRequest,
+ mPendingInlineSuggestionsRequest, id);
+ }
mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
- mPendingInlineSuggestionsRequest,
- mPendingFillRequest.getFlags(), id, mClient);
+ mPendingFillRequest.getFlags(), mClient);
} else if (mRemoteFillService != null) {
if (mIsPrimaryCredential) {
- mPendingFillRequest = addSessionIdAndRequestIdToClientState(mPendingFillRequest,
+ mPendingFillRequest = addCredentialManagerDataToClientState(
+ mPendingFillRequest,
mPendingInlineSuggestionsRequest, id);
mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
} else {
@@ -904,8 +909,9 @@
}
}
- private FillRequest addSessionIdAndRequestIdToClientState(FillRequest pendingFillRequest,
+ private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest,
InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) {
+
if (pendingFillRequest.getClientState() == null) {
pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
pendingFillRequest.getFillContexts(),
@@ -917,6 +923,10 @@
}
pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId);
pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId());
+ ResultReceiver resultReceiver = constructCredentialManagerCallback(
+ pendingFillRequest.getId());
+ pendingFillRequest.getClientState().putParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver);
return pendingFillRequest;
}
@@ -4870,10 +4880,6 @@
}
- if (isCredmanIntegrationActive(response)) {
- addCredentialManagerCallback(response);
- }
-
if (response.supportsInlineSuggestions()) {
synchronized (mLock) {
if (requestShowInlineSuggestionsLocked(response, filterText)) {
@@ -5153,30 +5159,14 @@
return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
}
- private void addCredentialManagerCallback(FillResponse response) {
- if (response.getDatasets() == null) {
- return;
- }
- for (Dataset dataset: response.getDatasets()) {
- if (dataset.getId() != null
- && dataset.getId().equals(AutofillManager.PINNED_DATASET_ID)) {
- Slog.d(TAG, "Adding Credential Manager callback to a pinned entry");
- addCredentialManagerCallbackForDataset(dataset, response.getRequestId());
- }
- }
- }
-
- private void addCredentialManagerCallbackForDataset(Dataset dataset, int requestId) {
- AutofillId autofillId = null;
- if (dataset != null && dataset.getFieldIds().size() == 1) {
- autofillId = dataset.getFieldIds().get(0);
- }
- final AutofillId finalAutofillId = autofillId;
+ private ResultReceiver constructCredentialManagerCallback(int requestId) {
final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ final AutofillId mAutofillId = mCurrentViewId;
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
- Slog.d(TAG, "onReceiveResult from Credential Manager bottom sheet");
+ Slog.d(TAG, "onReceiveResult from Credential Manager "
+ + "bottom sheet with mCurrentViewId: " + mAutofillId);
GetCredentialResponse getCredentialResponse =
resultData.getParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
@@ -5184,7 +5174,7 @@
if (Flags.autofillCredmanDevIntegration()) {
sendCredentialManagerResponseToApp(getCredentialResponse,
- /*exception=*/ null, finalAutofillId);
+ /*exception=*/ null, mAutofillId);
} else {
Dataset datasetFromCredential = getDatasetFromCredentialResponse(
getCredentialResponse);
@@ -5198,12 +5188,14 @@
String[] exception = resultData.getStringArray(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
if (exception != null && exception.length >= 2) {
+ String errType = exception[0];
+ String errMsg = exception[1];
Slog.w(TAG, "Credman bottom sheet from pinned "
- + "entry failed with: + " + exception[0] + " , "
- + exception[1]);
+ + "entry failed with: + " + errType + " , "
+ + errMsg);
sendCredentialManagerResponseToApp(/*response=*/ null,
- new GetCredentialException(exception[0], exception[1]),
- finalAutofillId);
+ new GetCredentialException(errType, errMsg),
+ mAutofillId);
}
} else {
Slog.d(TAG, "Unknown resultCode from credential "
@@ -5214,15 +5206,7 @@
ResultReceiver ipcFriendlyResultReceiver =
toIpcFriendlyResultReceiver(resultReceiver);
- Intent metadataIntent = dataset.getCredentialFillInIntent();
- if (metadataIntent == null) {
- metadataIntent = new Intent();
- }
-
- metadataIntent.putExtra(
- android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
- ipcFriendlyResultReceiver);
- dataset.setCredentialFillInIntent(metadataIntent);
+ return ipcFriendlyResultReceiver;
}
private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index cfb7f337..af49df6 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -56,6 +56,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.CollectionUtils;
import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
@@ -1031,6 +1032,9 @@
public void sendDevicePresenceEventOnUnlocked(int userId) {
final List<DevicePresenceEvent> deviceEvents = getPendingDevicePresenceEventsByUserId(
userId);
+ if (CollectionUtils.isEmpty(deviceEvents)) {
+ return;
+ }
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
// Notify and bind the app after the phone is unlocked.
@@ -1068,7 +1072,7 @@
}
}
- clearPendingDevicePresenceEventsByUserId(userId);
+ removePendingDevicePresenceEventsByUserId(userId);
}
private List<DevicePresenceEvent> getPendingDevicePresenceEventsByUserId(int userId) {
@@ -1077,9 +1081,11 @@
}
}
- private void clearPendingDevicePresenceEventsByUserId(int userId) {
+ private void removePendingDevicePresenceEventsByUserId(int userId) {
synchronized (mPendingDevicePresenceEvents) {
- mPendingDevicePresenceEvents.get(userId).clear();
+ if (mPendingDevicePresenceEvents.contains(userId)) {
+ mPendingDevicePresenceEvents.remove(userId);
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 23373f1..afeafa4 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -302,7 +302,7 @@
if (Flags.interceptIntentsBeforeApplyingPolicy()) {
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
if (!canContainActivity(activityInfo, windowingMode, launchingFromDisplayId,
@@ -318,7 +318,7 @@
}
if (mIntentListenerCallback != null && intent != null
&& mIntentListenerCallback.shouldInterceptIntent(intent)) {
- Slog.d(TAG, "Virtual device intercepting intent");
+ logActivityLaunchBlocked("Virtual device intercepting intent");
return false;
}
}
@@ -331,15 +331,17 @@
boolean isNewTask) {
// Mirror displays cannot contain activities.
if (waitAndGetIsMirrorDisplay()) {
- Slog.d(TAG, "Mirror virtual displays cannot contain activities.");
+ logActivityLaunchBlocked("Mirror virtual displays cannot contain activities.");
return false;
}
if (!isWindowingModeSupported(windowingMode)) {
- Slog.d(TAG, "Virtual device doesn't support windowing mode " + windowingMode);
+ logActivityLaunchBlocked(
+ "Virtual device doesn't support windowing mode " + windowingMode);
return false;
}
if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
- Slog.d(TAG, "Virtual device requires android:canDisplayOnRemoteDevices=true");
+ logActivityLaunchBlocked(
+ "Activity requires android:canDisplayOnRemoteDevices=true");
return false;
}
final UserHandle activityUser =
@@ -350,11 +352,11 @@
return true;
}
if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) {
- Slog.d(TAG, "Virtual device launch disallowed from user " + activityUser);
+ logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser);
return false;
}
if (!activityMatchesDisplayCategory(activityInfo)) {
- Slog.d(TAG, "The activity's required display category '"
+ logActivityLaunchBlocked("The activity's required display category '"
+ activityInfo.requiredDisplayCategory
+ "' not found on virtual display with the following categories: "
+ mDisplayCategories);
@@ -363,7 +365,7 @@
synchronized (mGenericWindowPolicyControllerLock) {
if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
activityComponent)) {
- Slog.d(TAG, "Virtual device launch disallowed by policy: "
+ logActivityLaunchBlocked("Activity launch disallowed by policy: "
+ activityComponent);
return false;
}
@@ -371,7 +373,7 @@
if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
&& !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
mCrossTaskNavigationExemptions, activityComponent)) {
- Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
+ logActivityLaunchBlocked("Cross task navigation disallowed by policy: "
+ activityComponent);
return false;
}
@@ -380,12 +382,18 @@
// based on FLAG_STREAM_PERMISSIONS
if (mPermissionDialogComponent != null
&& mPermissionDialogComponent.equals(activityComponent)) {
+ logActivityLaunchBlocked("Permission dialog not allowed on virtual device");
return false;
}
return true;
}
+ private void logActivityLaunchBlocked(String reason) {
+ Slog.d(TAG, "Virtual device activity launch disallowed on display "
+ + waitAndGetDisplayId() + ", reason: " + reason);
+ }
+
@Override
@SuppressWarnings("AndroidFrameworkRequiresPermission")
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
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 9b72288..3c25835 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -226,7 +226,7 @@
token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer());
String phys = inputDeviceDescriptor.getPhys();
- InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys);
+ InputManagerGlobal.getInstance().removeUniqueIdAssociationByDescriptor(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) {
@@ -319,9 +319,9 @@
return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement());
}
- private void setUniqueIdAssociation(int displayId, String phys) {
+ private void setUniqueIdAssociationByPort(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) {
@@ -809,7 +809,7 @@
final int inputDeviceId;
- setUniqueIdAssociation(displayId, phys);
+ setUniqueIdAssociationByPort(displayId, phys);
try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) {
ptr = deviceOpener.get();
// See INVALID_PTR in libs/input/VirtualInputDevice.cpp.
@@ -835,7 +835,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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8022eb3..ee5d49b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4437,8 +4437,16 @@
final boolean clearPendingIntentsForStoppedApp = (android.content.pm.Flags.stayStopped()
&& packageStateStopped);
if (packageName == null || uninstalling || clearPendingIntentsForStoppedApp) {
+ final int cancelReason;
+ if (packageName == null) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+ } else if (uninstalling) {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_UNINSTALLED;
+ } else {
+ cancelReason = PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+ }
didSomething |= mPendingIntentController.removePendingIntentsForPackage(
- packageName, userId, appId, doit);
+ packageName, userId, appId, doit, cancelReason);
}
if (doit) {
@@ -9181,6 +9189,11 @@
private class MyBinderProxyCountEventListener implements BinderProxyCountEventListener {
@Override
public void onLimitReached(int uid) {
+ // Spawn a new thread for the dump as it'll take long time.
+ new Thread(() -> handleLimitReached(uid), "BinderProxy Dump: " + uid).start();
+ }
+
+ private void handleLimitReached(int uid) {
Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
+ Process.myUid());
BinderProxy.dumpProxyDebugInfo();
@@ -10167,7 +10180,11 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)));
}
@@ -10182,13 +10199,30 @@
}
final int callingUid = Binder.getCallingUid();
- mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener, callingUid,
- true);
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "removeApplicationStartInfoCompleteListener", null);
+
+ mProcessList.getAppStartInfoTracker().removeStartInfoCompleteListener(listener,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), true);
}
@Override
public void addStartInfoTimestamp(int key, long timestampNs, int userId) {
enforceNotIsolatedCaller("addStartInfoTimestamp");
+
+ // For the simplification, we don't support USER_ALL nor USER_CURRENT here.
+ if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) {
+ throw new IllegalArgumentException("Unsupported userId");
+ }
+
+ final int callingUid = Binder.getCallingUid();
+ mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid, userId, true,
+ ALLOW_NON_FULL, "addStartInfoTimestamp", null);
+
+ final String packageName = Settings.getPackageNameForUid(mContext, callingUid);
+
+ mProcessList.getAppStartInfoTracker().addTimestampToStart(packageName,
+ UserHandle.getUid(userId, UserHandle.getAppId(callingUid)), timestampNs, key);
}
@Override
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index ddf1d5f..0728ea8 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -464,7 +464,7 @@
addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
}
- private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
+ void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
synchronized (mLock) {
AppStartInfoContainer container = mData.get(packageName, uid);
if (container == null) {
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index fb0d695..f336120 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -22,6 +22,8 @@
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
import android.annotation.Nullable;
import android.app.Activity;
@@ -54,6 +56,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalServices;
+import com.android.server.am.PendingIntentRecord.CancellationReason;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.SafeActivityOptions;
@@ -191,7 +194,7 @@
}
return rec;
}
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, CANCEL_REASON_SUPERSEDED);
mIntentSenderRecords.remove(key);
decrementUidStatLocked(rec);
}
@@ -206,7 +209,7 @@
}
boolean removePendingIntentsForPackage(String packageName, int userId, int appId,
- boolean doIt) {
+ boolean doIt, @CancellationReason int cancelReason) {
boolean didSomething = false;
synchronized (mLock) {
@@ -256,7 +259,7 @@
}
didSomething = true;
it.remove();
- makeIntentSenderCanceled(pir);
+ makeIntentSenderCanceled(pir, cancelReason);
decrementUidStatLocked(pir);
if (pir.key.activity != null) {
final Message m = PooledLambda.obtainMessage(
@@ -289,13 +292,14 @@
} catch (RemoteException e) {
throw new SecurityException(e);
}
- cancelIntentSender(rec, true);
+ cancelIntentSender(rec, true, CANCEL_REASON_OWNER_CANCELED);
}
}
- public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity) {
+ public void cancelIntentSender(PendingIntentRecord rec, boolean cleanActivity,
+ @CancellationReason int cancelReason) {
synchronized (mLock) {
- makeIntentSenderCanceled(rec);
+ makeIntentSenderCanceled(rec, cancelReason);
mIntentSenderRecords.remove(rec.key);
decrementUidStatLocked(rec);
if (cleanActivity && rec.key.activity != null) {
@@ -359,8 +363,10 @@
}
}
- private void makeIntentSenderCanceled(PendingIntentRecord rec) {
+ private void makeIntentSenderCanceled(PendingIntentRecord rec,
+ @CancellationReason int cancelReason) {
rec.canceled = true;
+ rec.cancelReason = cancelReason;
final RemoteCallbackList<IResultReceiver> callbacks = rec.detachCancelListenersLocked();
if (callbacks != null) {
final Message m = PooledLambda.obtainMessage(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 95e130e..da45a77 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -16,11 +16,13 @@
package com.android.server.am;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.START_SUCCESS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
@@ -51,11 +53,15 @@
import android.util.Slog;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.expresslog.Counter;
import com.android.server.wm.SafeActivityOptions;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -71,12 +77,35 @@
public static final int FLAG_BROADCAST_SENDER = 1 << 1;
public static final int FLAG_SERVICE_SENDER = 1 << 2;
+ public static final int CANCEL_REASON_NULL = 0;
+ public static final int CANCEL_REASON_USER_STOPPED = 1 << 0;
+ public static final int CANCEL_REASON_OWNER_UNINSTALLED = 1 << 1;
+ public static final int CANCEL_REASON_OWNER_FORCE_STOPPED = 1 << 2;
+ public static final int CANCEL_REASON_OWNER_CANCELED = 1 << 3;
+ public static final int CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED = 1 << 4;
+ public static final int CANCEL_REASON_SUPERSEDED = 1 << 5;
+ public static final int CANCEL_REASON_ONE_SHOT_SENT = 1 << 6;
+
+ @IntDef({
+ CANCEL_REASON_NULL,
+ CANCEL_REASON_USER_STOPPED,
+ CANCEL_REASON_OWNER_UNINSTALLED,
+ CANCEL_REASON_OWNER_FORCE_STOPPED,
+ CANCEL_REASON_OWNER_CANCELED,
+ CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED,
+ CANCEL_REASON_SUPERSEDED,
+ CANCEL_REASON_ONE_SHOT_SENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CancellationReason {}
+
final PendingIntentController controller;
final Key key;
final int uid;
public final WeakReference<PendingIntentRecord> ref;
boolean sent = false;
boolean canceled = false;
+ @CancellationReason int cancelReason = CANCEL_REASON_NULL;
/**
* Map IBinder to duration specified as Pair<Long, Integer>, Long is allowlist duration in
* milliseconds, Integer is allowlist type defined at
@@ -419,12 +448,22 @@
SafeActivityOptions mergedOptions = null;
synchronized (controller.mLock) {
if (canceled) {
+ if (cancelReason == CANCEL_REASON_OWNER_FORCE_STOPPED
+ && controller.mAmInternal.getUidProcessState(callingUid)
+ == PROCESS_STATE_TOP) {
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_caller",
+ callingUid);
+ Counter.logIncrementWithUid(
+ "app.value_force_stop_cancelled_pi_sent_from_top_per_owner",
+ uid);
+ }
return ActivityManager.START_CANCELED;
}
sent = true;
if ((key.flags & PendingIntent.FLAG_ONE_SHOT) != 0) {
- controller.cancelIntentSender(this, true);
+ controller.cancelIntentSender(this, true, CANCEL_REASON_ONE_SHOT_SENT);
}
finalIntent = key.requestIntent != null ? new Intent(key.requestIntent) : new Intent();
@@ -687,6 +726,21 @@
}
}
+ @VisibleForTesting
+ static String cancelReasonToString(@CancellationReason int cancelReason) {
+ return switch (cancelReason) {
+ case CANCEL_REASON_NULL -> "NULL";
+ case CANCEL_REASON_USER_STOPPED -> "USER_STOPPED";
+ case CANCEL_REASON_OWNER_UNINSTALLED -> "OWNER_UNINSTALLED";
+ case CANCEL_REASON_OWNER_FORCE_STOPPED -> "OWNER_FORCE_STOPPED";
+ case CANCEL_REASON_OWNER_CANCELED -> "OWNER_CANCELED";
+ case CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED -> "HOSTING_ACTIVITY_DESTROYED";
+ case CANCEL_REASON_SUPERSEDED -> "SUPERSEDED";
+ case CANCEL_REASON_ONE_SHOT_SENT -> "ONE_SHOT_SENT";
+ default -> "UNKNOWN";
+ };
+ }
+
public void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("uid="); pw.print(uid);
pw.print(" packageName="); pw.print(key.packageName);
@@ -707,7 +761,8 @@
}
if (sent || canceled) {
pw.print(prefix); pw.print("sent="); pw.print(sent);
- pw.print(" canceled="); pw.println(canceled);
+ pw.print(" canceled="); pw.print(canceled);
+ pw.print(" cancelReason="); pw.println(cancelReasonToString(cancelReason));
}
if (mAllowlistDuration != null) {
pw.print(prefix);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index be39778..a1f80d0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -70,6 +70,7 @@
import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
+import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled;
import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
@@ -248,6 +249,7 @@
Process.ROOT_UID,
Process.PHONE_UID,
Process.BLUETOOTH_UID,
+ Process.AUDIOSERVER_UID,
Process.NFC_UID,
Process.NETWORK_STACK_UID,
Process.SHELL_UID};
@@ -2682,6 +2684,15 @@
}
}
+ /**
+ * When querying the mode these should always be allowed and the checking service might not
+ * have information on them.
+ */
+ private static boolean isOpAllowedForUid(int uid) {
+ return runtimePermissionAppopsMappingEnabled()
+ && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID);
+ }
+
@Override
public int checkOperationRaw(int code, int uid, String packageName,
@Nullable String attributionTag) {
@@ -2757,6 +2768,9 @@
pvr.bypass, true)) {
return AppOpsManager.MODE_IGNORED;
}
+ if (isOpAllowedForUid(uid)) {
+ return MODE_ALLOWED;
+ }
code = AppOpsManager.opToSwitch(code);
UidState uidState = getUidStateLocked(uid, false);
if (uidState != null
@@ -3071,9 +3085,12 @@
return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
packageName);
}
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (mAppOpsCheckingService.getUidMode(
+ if (isOpAllowedForUid(uid)) {
+ // Op is always allowed for the UID, do nothing.
+
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ } else if (mAppOpsCheckingService.getUidMode(
uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
@@ -3665,10 +3682,13 @@
isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag,
virtualDeviceId, pvr.bypass, false);
final int switchCode = AppOpsManager.opToSwitch(code);
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (mAppOpsCheckingService.getUidMode(
- uidState.uid, getPersistentId(virtualDeviceId), switchCode)
+ if (isOpAllowedForUid(uid)) {
+ // Op is always allowed for the UID, do nothing.
+
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ } else if (mAppOpsCheckingService.getUidMode(
+ uidState.uid, getPersistentId(virtualDeviceId), switchCode)
!= AppOpsManager.opToDefaultMode(switchCode)) {
final int uidMode =
uidState.evalMode(
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
index 570d4e9..e330ed5 100644
--- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -16,6 +16,11 @@
package com.android.server.audio;
+import static android.media.AudioManager.ADJUST_LOWER;
+import static android.media.AudioManager.ADJUST_MUTE;
+import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_UNMUTE;
+
import android.content.Context;
import android.media.AudioManager;
import android.os.ShellCommand;
@@ -59,6 +64,12 @@
return adjMute();
case "adj-unmute":
return adjUnmute();
+ case "adj-volume":
+ return adjVolume();
+ case "set-group-volume":
+ return setGroupVolume();
+ case "adj-group-volume":
+ return adjGroupVolume();
}
return 0;
}
@@ -90,6 +101,12 @@
pw.println(" mutes the STREAM_TYPE");
pw.println(" adj-unmute STREAM_TYPE");
pw.println(" unmutes the STREAM_TYPE");
+ pw.println(" adj-volume STREAM_TYPE <RAISE|LOWER|MUTE|UNMUTE>");
+ pw.println(" Adjusts the STREAM_TYPE volume given the specified direction");
+ pw.println(" set-group-volume GROUP_ID VOLUME_INDEX");
+ pw.println(" Sets the volume for GROUP_ID to VOLUME_INDEX");
+ pw.println(" adj-group-volume GROUP_ID <RAISE|LOWER|MUTE|UNMUTE>");
+ pw.println(" Adjusts the group volume for GROUP_ID given the specified direction");
}
private int setSurroundFormatEnabled() {
@@ -260,15 +277,48 @@
return 0;
}
+ private int adjVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int stream = readIntArg();
+ final int direction = readDirectionArg();
+ getOutPrintWriter().println("calling AudioManager.adjustStreamVolume("
+ + stream + ", " + direction + ", 0)");
+ am.adjustStreamVolume(stream, direction, 0);
+ return 0;
+ }
+
+ private int setGroupVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int groupId = readIntArg();
+ final int index = readIntArg();
+ getOutPrintWriter().println("calling AudioManager.setVolumeGroupVolumeIndex("
+ + groupId + ", " + index + ", 0)");
+ am.setVolumeGroupVolumeIndex(groupId, index, 0);
+ return 0;
+ }
+
+ private int adjGroupVolume() {
+ final Context context = mService.mContext;
+ final AudioManager am = context.getSystemService(AudioManager.class);
+ final int groupId = readIntArg();
+ final int direction = readDirectionArg();
+ getOutPrintWriter().println("calling AudioManager.adjustVolumeGroupVolume("
+ + groupId + ", " + direction + ", 0)");
+ am.adjustVolumeGroupVolume(groupId, direction, 0);
+ return 0;
+ }
+
private int readIntArg() throws IllegalArgumentException {
- String argText = getNextArg();
+ final String argText = getNextArg();
if (argText == null) {
getErrPrintWriter().println("Error: no argument provided");
throw new IllegalArgumentException("No argument provided");
}
- int argIntVal = Integer.MIN_VALUE;
+ int argIntVal;
try {
argIntVal = Integer.parseInt(argText);
} catch (NumberFormatException e) {
@@ -278,4 +328,21 @@
return argIntVal;
}
+
+ private int readDirectionArg() throws IllegalArgumentException {
+ final String argText = getNextArg();
+
+ if (argText == null) {
+ getErrPrintWriter().println("Error: no argument provided");
+ throw new IllegalArgumentException("No argument provided");
+ }
+
+ return switch (argText) {
+ case "RAISE" -> ADJUST_RAISE;
+ case "LOWER" -> ADJUST_LOWER;
+ case "MUTE" -> ADJUST_MUTE;
+ case "UNMUTE" -> ADJUST_UNMUTE;
+ default -> throw new IllegalArgumentException("Wrong direction argument: " + argText);
+ };
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 7df63b1..11cca66 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -25,15 +25,12 @@
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.Manifest.permission.USE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -778,13 +775,7 @@
hidlConfigs = null;
}
- if (com.android.server.biometrics.Flags.deHidl()) {
- registerAuthenticators();
- } else {
- // Registers HIDL and AIDL authenticators, but only HIDL configs need to be provided.
- registerAuthenticators(hidlConfigs);
- }
-
+ registerAuthenticators();
mInjector.publishBinderService(this, mImpl);
}
@@ -874,7 +865,7 @@
if (faceService != null) {
try {
- faceService.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ faceService.registerAuthenticators(mFaceSensorConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when registering face authenticators", e);
}
@@ -912,8 +903,7 @@
if (fingerprintService != null) {
try {
- fingerprintService.registerAuthenticatorsLegacy(
- mFingerprintSensorConfigurations);
+ fingerprintService.registerAuthenticators(mFingerprintSensorConfigurations);
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
}
@@ -948,78 +938,6 @@
return configStrings;
}
- /**
- * Registers HIDL and AIDL authenticators for all of the available modalities.
- *
- * @param hidlSensors Array of {@link SensorConfig} configuration for all of the HIDL sensors
- * available on the device. This array may contain configuration for
- * different modalities and different sensors of the same modality in
- * arbitrary order. Can be null if no HIDL sensors exist on the device.
- */
- private void registerAuthenticators(@Nullable SensorConfig[] hidlSensors) {
- List<FingerprintSensorPropertiesInternal> hidlFingerprintSensors = new ArrayList<>();
- List<FaceSensorPropertiesInternal> hidlFaceSensors = new ArrayList<>();
- // Iris doesn't have IrisSensorPropertiesInternal, using SensorPropertiesInternal instead.
- List<SensorPropertiesInternal> hidlIrisSensors = new ArrayList<>();
-
- if (hidlSensors != null) {
- for (SensorConfig sensor : hidlSensors) {
- Slog.d(TAG, "Registering HIDL ID: " + sensor.id + " Modality: " + sensor.modality
- + " Strength: " + sensor.strength);
- switch (sensor.modality) {
- case TYPE_FINGERPRINT:
- hidlFingerprintSensors.add(
- getHidlFingerprintSensorProps(sensor.id, sensor.strength));
- break;
-
- case TYPE_FACE:
- hidlFaceSensors.add(getHidlFaceSensorProps(sensor.id, sensor.strength));
- break;
-
- case TYPE_IRIS:
- hidlIrisSensors.add(getHidlIrisSensorProps(sensor.id, sensor.strength));
- break;
-
- default:
- Slog.e(TAG, "Unknown modality: " + sensor.modality);
- }
- }
- }
-
- final IFingerprintService fingerprintService = mInjector.getFingerprintService();
- if (fingerprintService != null) {
- try {
- fingerprintService.registerAuthenticators(hidlFingerprintSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering fingerprint authenticators", e);
- }
- } else if (hidlFingerprintSensors.size() > 0) {
- Slog.e(TAG, "HIDL fingerprint configuration exists, but FingerprintService is null.");
- }
-
- final IFaceService faceService = mInjector.getFaceService();
- if (faceService != null) {
- try {
- faceService.registerAuthenticators(hidlFaceSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering face authenticators", e);
- }
- } else if (hidlFaceSensors.size() > 0) {
- Slog.e(TAG, "HIDL face configuration exists, but FaceService is null.");
- }
-
- final IIrisService irisService = mInjector.getIrisService();
- if (irisService != null) {
- try {
- irisService.registerAuthenticators(hidlIrisSensors);
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException when registering iris authenticators", e);
- }
- } else if (hidlIrisSensors.size() > 0) {
- Slog.e(TAG, "HIDL iris configuration exists, but IrisService is null.");
- }
- }
-
private void checkInternalPermission() {
getContext().enforceCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL,
"Must have USE_BIOMETRIC_INTERNAL permission");
diff --git a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
index e578861..91cabb5 100644
--- a/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
+++ b/services/core/java/com/android/server/biometrics/BiometricHandlerProvider.java
@@ -21,7 +21,6 @@
import android.os.Handler;
import android.os.HandlerThread;
-import android.os.Looper;
/**
* This class provides the handler to process biometric operations.
@@ -76,11 +75,8 @@
}
private Handler getNewHandler(String tag, int priority) {
- if (Flags.deHidl()) {
- HandlerThread handlerThread = new HandlerThread(tag, priority);
- handlerThread.start();
- return new Handler(handlerThread.getLooper());
- }
- return new Handler(Looper.getMainLooper());
+ HandlerThread handlerThread = new HandlerThread(tag, priority);
+ handlerThread.start();
+ return new Handler(handlerThread.getLooper());
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContext.java b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
index 7f04628..7a8e25b 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContext.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContext.java
@@ -81,22 +81,6 @@
boolean isHardwareIgnoringTouches();
/**
- * Subscribe to context changes.
- *
- * Note that this method only notifies for properties that are visible to the HAL.
- *
- * @param context context that will be modified when changed
- * @param consumer callback when the context is modified
- *
- * @deprecated instead use {@link BiometricContext#subscribe(OperationContextExt, Consumer,
- * Consumer, AuthenticateOptions)}
- * TODO (b/294161627): Delete this API once Flags.DE_HIDL is removed.
- */
- @Deprecated
- void subscribe(@NonNull OperationContextExt context,
- @NonNull Consumer<OperationContext> consumer);
-
- /**
* Subscribe to context changes and start the HAL operation.
*
* Note that this method only notifies for properties that are visible to the HAL.
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
index d8dfa60..a17de3d 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricContextProvider.java
@@ -229,16 +229,6 @@
@Override
public void subscribe(@NonNull OperationContextExt context,
- @NonNull Consumer<OperationContext> consumer) {
- mSubscribers.put(context, consumer);
- // TODO(b/294161627) Combine the getContext/subscribe APIs to avoid race
- if (context.getDisplayState() != getDisplayState()) {
- consumer.accept(context.update(this, context.isCrypto()).toAidlContext());
- }
- }
-
- @Override
- public void subscribe(@NonNull OperationContextExt context,
@NonNull Consumer<OperationContext> startHalConsumer,
@NonNull Consumer<OperationContext> updateContextConsumer,
@Nullable AuthenticateOptions options) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 4fa8741..1e2451c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -35,7 +35,6 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -117,24 +116,20 @@
@LockoutTracker.LockoutMode
public int handleFailedAttempt(int userId) {
- if (Flags.deHidl()) {
- if (mLockoutTracker != null) {
- mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
- }
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(userId);
- final PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
- if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
- performanceTracker.incrementPermanentLockoutForUser(userId);
- } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
- performanceTracker.incrementTimedLockoutForUser(userId);
- }
-
- return lockoutMode;
- } else {
- return LockoutTracker.LOCKOUT_NONE;
+ if (mLockoutTracker != null) {
+ mLockoutTracker.addFailedAttemptForUser(getTargetUserId());
}
+ @LockoutTracker.LockoutMode final int lockoutMode =
+ getLockoutTracker().getLockoutModeForUser(userId);
+ final PerformanceTracker performanceTracker =
+ PerformanceTracker.getInstanceForSensorId(getSensorId());
+ if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
+ performanceTracker.incrementPermanentLockoutForUser(userId);
+ } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
+ performanceTracker.incrementTimedLockoutForUser(userId);
+ }
+
+ return lockoutMode;
}
protected long getStartTimeMs() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index 89e08c1..82d5d4d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -19,9 +19,9 @@
import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
import android.annotation.IntDef;
-import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -38,7 +38,6 @@
import com.android.modules.expresslog.Counter;
import com.android.server.biometrics.BiometricSchedulerProto;
import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.io.PrintWriter;
@@ -65,9 +64,8 @@
* @param <T> Hal instance for starting the user.
* @param <U> Session associated with the current user id.
*
- * TODO: (b/304604965) Update thread annotation when FLAGS_DE_HIDL is removed.
*/
-@MainThread
+@WorkerThread
public class BiometricScheduler<T, U> {
private static final String TAG = "BiometricScheduler";
@@ -176,7 +174,7 @@
Slog.w(TAG, "operation is already null or different (reset?): "
+ mCurrentOperation);
}
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
});
}
}
@@ -219,7 +217,7 @@
mRecentOperations.add(mCurrentOperation.getProtoEnum());
mCurrentOperation = null;
mTotalOperationsHandled++;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
});
}
};
@@ -304,15 +302,7 @@
return mInternalCallback;
}
- protected void startNextOperationIfIdle() {
- if (Flags.deHidl()) {
- startNextOperation();
- } else {
- startNextOperationIfIdleLegacy();
- }
- }
-
- protected void startNextOperation() {
+ protected void checkCurrentUserAndStartNextOperation() {
if (mCurrentOperation != null) {
Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
return;
@@ -326,7 +316,7 @@
final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
- startNextOperationIfIdleLegacy();
+ startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL && mUserSwitchProvider != null) {
final BaseClientMonitor startClient =
mUserSwitchProvider.getStartUserClient(nextUserId);
@@ -357,7 +347,7 @@
}
}
- protected void startNextOperationIfIdleLegacy() {
+ protected void startNextOperationIfIdle() {
if (mCurrentOperation != null) {
Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
return;
@@ -422,7 +412,7 @@
// run these. A single request from the manager layer to the service layer may
// actually be multiple operations (i.e. updateActiveUser + authenticate).
mCurrentOperation = null;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
} else {
try {
@@ -459,7 +449,7 @@
} else {
Slog.e(TAG, "[Unable To Start] Prepared client: " + mCurrentOperation);
mCurrentOperation = null;
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
}
@@ -504,7 +494,7 @@
Slog.d(TAG, "[Cancelling Interruptable]: " + mCurrentOperation);
mCurrentOperation.cancel(mHandler, mInternalCallback);
} else {
- startNextOperationIfIdle();
+ checkCurrentUserAndStartNextOperation();
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
deleted file mode 100644
index 7ca10e3..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics.sensors;
-
-import static com.android.server.biometrics.sensors.BiometricSchedulerOperation.STATE_STARTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.IBiometricService;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-/**
- * A user-aware scheduler that requests user-switches based on scheduled operation's targetUserId.
- * TODO (b/304604965): Remove class when Flags.FLAG_DE_HIDL is removed.
- *
- * @param <T> Hal instance for starting the user.
- * @param <U> Session associated with the current user id.
- */
-public class UserAwareBiometricScheduler<T, U> extends BiometricScheduler<T, U> {
-
- private static final String TAG = "UaBiometricScheduler";
-
- /**
- * Interface to retrieve the owner's notion of the current userId. Note that even though
- * the scheduler can determine this based on its history of processed clients, we should still
- * query the owner since it may be cleared due to things like HAL death, etc.
- */
- public interface CurrentUserRetriever {
- int getCurrentUserId();
- }
-
- public interface UserSwitchCallback {
- @NonNull StopUserClient<?> getStopUserClient(int userId);
- @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
- }
-
- @NonNull private final CurrentUserRetriever mCurrentUserRetriever;
- @NonNull private final UserSwitchCallback mUserSwitchCallback;
- @Nullable private StopUserClient<?> mStopUserClient;
-
- private class ClientFinishedCallback implements ClientMonitorCallback {
- @NonNull private final BaseClientMonitor mOwner;
-
- ClientFinishedCallback(@NonNull BaseClientMonitor owner) {
- mOwner = owner;
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
- mHandler.post(() -> {
- Slog.d(TAG, "[Client finished] " + clientMonitor + ", success: " + success);
-
- // Set mStopUserClient to null when StopUserClient fails. Otherwise it's possible
- // for that the queue will wait indefinitely until the field is cleared.
- if (clientMonitor instanceof StopUserClient<?>) {
- if (!success) {
- Slog.w(TAG, "StopUserClient failed(), is the HAL stuck? "
- + "Clearing mStopUserClient");
- }
- mStopUserClient = null;
- }
- if (mCurrentOperation != null && mCurrentOperation.isFor(mOwner)) {
- mCurrentOperation = null;
- } else {
- // can happen if the hal dies and is usually okay
- // do not unset the current operation that may be newer
- Slog.w(TAG, "operation is already null or different (reset?): "
- + mCurrentOperation);
- }
- startNextOperationIfIdle();
- });
- }
- }
-
- @VisibleForTesting
- public UserAwareBiometricScheduler(@NonNull String tag,
- @NonNull Handler handler,
- @SensorType int sensorType,
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull IBiometricService biometricService,
- @NonNull CurrentUserRetriever currentUserRetriever,
- @NonNull UserSwitchCallback userSwitchCallback) {
- super(handler, sensorType, gestureAvailabilityDispatcher, biometricService,
- LOG_NUM_RECENT_OPERATIONS);
-
- mCurrentUserRetriever = currentUserRetriever;
- mUserSwitchCallback = userSwitchCallback;
- }
-
- public UserAwareBiometricScheduler(@NonNull String tag,
- @SensorType int sensorType,
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull CurrentUserRetriever currentUserRetriever,
- @NonNull UserSwitchCallback userSwitchCallback) {
- this(tag, new Handler(Looper.getMainLooper()), sensorType, gestureAvailabilityDispatcher,
- IBiometricService.Stub.asInterface(
- ServiceManager.getService(Context.BIOMETRIC_SERVICE)),
- currentUserRetriever, userSwitchCallback);
- }
-
- @Override
- protected void startNextOperationIfIdle() {
- if (mCurrentOperation != null) {
- Slog.v(TAG, "Not idle, current operation: " + mCurrentOperation);
- return;
- }
- if (mPendingOperations.isEmpty()) {
- Slog.d(TAG, "No operations, returning to idle");
- return;
- }
-
- final int currentUserId = mCurrentUserRetriever.getCurrentUserId();
- final int nextUserId = mPendingOperations.getFirst().getTargetUserId();
-
- if (nextUserId == currentUserId || mPendingOperations.getFirst().isStartUserOperation()) {
- super.startNextOperationIfIdle();
- } else if (currentUserId == UserHandle.USER_NULL) {
- final BaseClientMonitor startClient =
- mUserSwitchCallback.getStartUserClient(nextUserId);
- final ClientFinishedCallback finishedCallback =
- new ClientFinishedCallback(startClient);
-
- Slog.d(TAG, "[Starting User] " + startClient);
- mCurrentOperation = new BiometricSchedulerOperation(
- startClient, finishedCallback, STATE_STARTED);
- startClient.start(finishedCallback);
- } else {
- if (mStopUserClient != null) {
- Slog.d(TAG, "[Waiting for StopUser] " + mStopUserClient);
- } else {
- mStopUserClient = mUserSwitchCallback
- .getStopUserClient(currentUserId);
- final ClientFinishedCallback finishedCallback =
- new ClientFinishedCallback(mStopUserClient);
-
- Slog.d(TAG, "[Stopping User] current: " + currentUserId
- + ", next: " + nextUserId + ". " + mStopUserClient);
- mCurrentOperation = new BiometricSchedulerOperation(
- mStopUserClient, finishedCallback, STATE_STARTED);
- mStopUserClient.start(finishedCallback);
- }
- }
- }
-
- @Override
- public void onUserStopped() {
- if (mStopUserClient == null) {
- Slog.e(TAG, "Unexpected onUserStopped");
- return;
- }
-
- Slog.d(TAG, "[OnUserStopped]: " + mStopUserClient);
- mStopUserClient.onUserStopped();
- mStopUserClient = null;
- }
-
- @VisibleForTesting
- @Nullable public StopUserClient<?> getStopUserClient() {
- return mStopUserClient;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index a946af8..bd6d593 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -72,9 +72,6 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
-import com.android.server.biometrics.sensors.face.hidl.Face10;
-
-import com.google.android.collect.Lists;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -664,60 +661,11 @@
provider.second.scheduleGetFeature(provider.first, token, userId, feature,
new ClientMonitorCallbackConverter(receiver), opPackageName);
}
- @NonNull
- private List<ServiceProvider> getHidlProviders(
- @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
- providers.add(
- Face10.newInstance(getContext(), mBiometricStateCallback,
- mAuthenticationStateListeners, hidlSensor,
- mLockoutResetDispatcher));
- }
-
- return providers;
- }
-
- @NonNull
- private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (String instance : instances) {
- final FaceProvider provider = mFaceProvider.apply(instance);
- Slog.i(TAG, "Adding AIDL provider: " + instance);
- providers.add(provider);
- }
-
- return providers;
- }
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
public void registerAuthenticators(
- @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
- super.registerAuthenticators_enforcePermission();
-
- mRegistry.registerAll(() -> {
- List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
- if (instances != null) {
- aidlSensors.addAll(Lists.newArrayList(instances));
- }
-
- final Pair<List<FaceSensorPropertiesInternal>, List<String>>
- filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(filteredInstances.first));
- providers.addAll(getAidlProviders(filteredInstances.second));
- return providers;
- });
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- public void registerAuthenticatorsLegacy(
FaceSensorConfigurations faceSensorConfigurations) {
- super.registerAuthenticatorsLegacy_enforcePermission();
+ super.registerAuthenticators_enforcePermission();
if (!faceSensorConfigurations.hasSensorConfigurations()) {
Slog.d(TAG, "No face sensors to register.");
@@ -771,40 +719,6 @@
.getSensorPropForInstance(finalSensorInstance));
}
- private Pair<List<FaceSensorPropertiesInternal>, List<String>>
- filterAvailableHalInstances(
- @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
- @NonNull List<String> aidlInstances) {
- if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
- return new Pair(hidlInstances, aidlInstances);
- }
-
- if (Flags.faceVhalFeature()) {
- Slog.i(TAG, "Face VHAL feature is on");
- } else {
- Slog.i(TAG, "Face VHAL feature is off");
- }
-
- final int virtualAt = aidlInstances.indexOf("virtual");
- if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
- if (virtualAt != -1) {
- //only virtual instance should be returned
- Slog.i(TAG, "virtual hal is used");
- return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
- } else {
- Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return new Pair(hidlInstances, aidlInstances);
- }
- } else {
- //remove virtual instance
- aidlInstances = new ArrayList<>(aidlInstances);
- if (virtualAt != -1) {
- aidlInstances.remove(virtualAt);
- }
- return new Pair(hidlInstances, aidlInstances);
- }
- }
-
@Override
public void addAuthenticatorsRegisteredCallback(
IFaceAuthenticatorsRegisteredCallback callback) {
@@ -883,17 +797,13 @@
return null;
};
- if (Flags.deHidl()) {
- mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
- ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
- getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
- filteredSensorProps.second,
- filteredSensorProps.first, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
- resetLockoutRequiresChallenge));
- } else {
- mFaceProviderFunction = ((filteredSensorProps, resetLockoutRequiresChallenge) -> null);
- }
+ mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
+ ((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
+ getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
+ filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()),
+ resetLockoutRequiresChallenge));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
index 098be21..cf677d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/AidlResponseHandler.java
@@ -27,7 +27,6 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
/**
* Interface to send results to the AidlResponseHandler's owner.
*/
- public interface HardwareUnavailableCallback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- /**
- * Interface to send results to the AidlResponseHandler's owner.
- */
public interface AidlResponseHandlerCallback {
/**
* Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
- private final HardwareUnavailableCallback mHardwareUnavailableCallback;
- @NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
- this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
- authSessionCoordinator, hardwareUnavailableCallback,
- new AidlResponseHandlerCallback() {
- @Override
- public void onEnrollSuccess() {}
-
- @Override
- public void onHardwareUnavailable() {}
- });
- }
-
- public AidlResponseHandler(@NonNull Context context,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutTracker lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
@NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
@@ -125,7 +94,6 @@
mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
- mHardwareUnavailableCallback = hardwareUnavailableCallback;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@@ -185,11 +153,7 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- if (Flags.deHidl()) {
- mAidlResponseHandlerCallback.onHardwareUnavailable();
- } else {
- mHardwareUnavailableCallback.onHardwareUnavailable();
- }
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
}
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 415d294..c43c7d9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -20,7 +20,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
@@ -39,7 +38,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -70,8 +68,6 @@
private final UsageStats mUsageStats;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
- @Nullable
- private final NotificationManager mNotificationManager;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
private final int[] mKeyguardIgnoreList;
@@ -123,7 +119,6 @@
biometricStrength);
setRequestId(requestId);
mUsageStats = usageStats;
- mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = sensorPrivacyManager;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
mAuthenticationStateListeners = authenticationStateListeners;
@@ -163,11 +158,7 @@
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
} else {
- if (Flags.deHidl()) {
- startAuthenticate();
- } else {
- mCancellationSignal = doAuthenticate();
- }
+ doAuthenticate();
}
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
@@ -176,27 +167,7 @@
}
}
- private ICancellationSignal doAuthenticate() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext(getOptions()));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().authenticate(mOperationId);
- }
- }
-
- private void startAuthenticate() throws RemoteException {
+ private void doAuthenticate() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 5ddddda..dcd94896 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -29,7 +29,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -112,38 +111,14 @@
}
try {
- if (Flags.deHidl()) {
- startDetect();
- } else {
- mCancellationSignal = doDetectInteraction();
- }
+ doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting face detect", e);
mCallback.onClientFinished(this, false /* success */);
}
}
- private ICancellationSignal doDetectInteraction() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
- opContext.toAidlContext(mOptions));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().detectInteraction();
- }
- }
-
- private void startDetect() throws RemoteException {
+ private void doDetectInteraction() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 781e3f4..73e8ece 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -36,7 +36,6 @@
import android.view.Surface;
import com.android.internal.R;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
@@ -193,11 +192,7 @@
features[i] = featureList.get(i);
}
- if (Flags.deHidl()) {
- startEnroll(features);
- } else {
- mCancellationSignal = doEnroll(features);
- }
+ doEnroll(features);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Exception when requesting enroll", e);
onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
@@ -205,43 +200,7 @@
}
}
- private ICancellationSignal doEnroll(byte[] features) throws RemoteException {
- final AidlSession session = getFreshDaemon();
- final HardwareAuthToken hat =
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- ICancellationSignal cancel;
- if (session.supportsFaceEnrollOptions()) {
- FaceEnrollOptions options = new FaceEnrollOptions();
- options.hardwareAuthToken = hat;
- options.enrollmentType = EnrollmentType.DEFAULT;
- options.features = features;
- options.nativeHandlePreview = null;
- options.context = opContext.toAidlContext();
- options.surfacePreview = mPreviewSurface;
- cancel = session.getSession().enrollWithOptions(options);
- } else {
- cancel = session.getSession().enrollWithContext(
- hat, EnrollmentType.DEFAULT, features, mHwPreviewHandle,
- opContext.toAidlContext());
- }
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().enroll(hat, EnrollmentType.DEFAULT, features,
- mHwPreviewHandle);
- }
- }
-
- private void startEnroll(byte[] features) throws RemoteException {
+ private void doEnroll(byte[] features) throws RemoteException {
final AidlSession session = getFreshDaemon();
final HardwareAuthToken hat =
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 11db183..75b4fd3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -27,11 +27,9 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
@@ -43,7 +41,6 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -56,7 +53,6 @@
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -193,11 +189,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
- if (Flags.deHidl()) {
- mHandler = biometricHandlerProvider.getFaceHandler();
- } else {
- mHandler = new Handler(Looper.getMainLooper());
- }
+ mHandler = biometricHandlerProvider.getFaceHandler();
mUsageStats = new UsageStats(context);
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -223,50 +215,15 @@
}
private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) {
- if (Flags.deHidl()) {
- if (resetLockoutRequiresChallenge) {
- Slog.d(getTag(), "Adding HIDL configs");
- for (SensorProps prop : props) {
- addHidlSensors(prop, resetLockoutRequiresChallenge);
- }
- } else {
- Slog.d(getTag(), "Adding AIDL configs");
- for (SensorProps prop : props) {
- addAidlSensors(prop, resetLockoutRequiresChallenge);
- }
+ if (resetLockoutRequiresChallenge) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps prop : props) {
+ addHidlSensors(prop, resetLockoutRequiresChallenge);
}
} else {
+ Slog.d(getTag(), "Adding AIDL configs");
for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
-
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
- }
- }
-
- final FaceSensorPropertiesInternal internalProp = new FaceSensorPropertiesInternal(
- prop.commonProps.sensorId, prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType,
- prop.supportsDetectInteraction, prop.halControlsPreview,
- false /* resetLockoutRequiresChallenge */);
- final Sensor sensor = new Sensor(this,
- mContext, mHandler, internalProp,
- mBiometricContext);
- sensor.init(mLockoutResetDispatcher, this);
- final int userId = sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFaceSensors.addSensor(sensorId, sensor, userId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + internalProp);
+ addAidlSensors(prop, resetLockoutRequiresChallenge);
}
}
}
@@ -477,12 +434,7 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- if (Flags.deHidl()) {
- return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
- } else {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
- }
+ return mFaceSensors.get(sensorId).getLockoutModeForUser(userId);
}
@Override
@@ -492,11 +444,7 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- if (Flags.deHidl()) {
- return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
- } else {
- return hasHalInstance();
- }
+ return mFaceSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
}
@Override
@@ -549,23 +497,7 @@
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent, options);
- if (Flags.deHidl()) {
- scheduleForSensor(sensorId, client, mBiometricStateCallback);
- } else {
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(clientMonitor,
- success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
- }
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
}
@@ -614,12 +546,8 @@
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
- final LockoutTracker lockoutTracker;
- if (Flags.deHidl()) {
- lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(true /* forAuth */);
- } else {
- lockoutTracker = null;
- }
+ final LockoutTracker lockoutTracker = mFaceSensors.get(sensorId).getLockoutTracker(
+ true /* forAuth */);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -634,29 +562,18 @@
@Override
public void onClientStarted(
BaseClientMonitor clientMonitor) {
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authStartedFor(userId, sensorId,
- requestId));
- } else {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
}
@Override
public void onClientFinished(
BaseClientMonitor clientMonitor,
boolean success) {
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId,
- client.wasAuthSuccessful()));
- } else {
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId),
- sensorId, requestId, client.wasAuthSuccessful());
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ client.wasAuthSuccessful()));
}
});
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 635e79a..6b99493 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -42,7 +42,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -57,7 +56,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import com.android.server.biometrics.sensors.face.FaceUtils;
@@ -89,7 +87,6 @@
@Nullable AidlSession mCurrentSession;
@NonNull BiometricContext mBiometricContext;
-
Sensor(@NonNull FaceProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull FaceSensorPropertiesInternal sensorProperties,
@NonNull BiometricContext biometricContext) {
@@ -116,11 +113,7 @@
*/
public void init(@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull FaceProvider provider) {
- if (Flags.deHidl()) {
- setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
- } else {
- setScheduler(getUserAwareBiometricSchedulerForInit(lockoutResetDispatcher, provider));
- }
+ setScheduler(getBiometricSchedulerForInit(lockoutResetDispatcher, provider));
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
mLockoutTracker = new LockoutCache();
}
@@ -149,8 +142,7 @@
final AidlResponseHandler resultController = new AidlResponseHandler(
mContext, mScheduler, sensorId, newUserId,
mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- },
+ mBiometricContext.getAuthSessionCoordinator(),
new AidlResponseHandler.AidlResponseHandlerCallback() {
@Override
public void onEnrollSuccess() {
@@ -173,40 +165,6 @@
});
}
- private UserAwareBiometricScheduler<IFace, ISession> getUserAwareBiometricSchedulerForInit(
- LockoutResetDispatcher lockoutResetDispatcher,
- FaceProvider provider) {
- return new UserAwareBiometricScheduler<>(TAG,
- BiometricScheduler.SENSOR_TYPE_FACE, null /* gestureAvailabilityDispatcher */,
- () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new FaceStopUserClient(mContext,
- () -> mLazySession.get().getSession(), mToken, userId,
- mSensorProperties.sensorId, BiometricLogger.ofUnknown(mContext),
- mBiometricContext, () -> mCurrentSession = null);
- }
-
- @NonNull
- @Override
- public StartUserClient<IFace, ISession> getStartUserClient(int newUserId) {
- final int sensorId = mSensorProperties.sensorId;
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(TAG, "Face sensor hardware unavailable.");
- mCurrentSession = null;
- });
-
- return Sensor.this.getStartUserClient(resultController, sensorId,
- newUserId, provider);
- }
- });
- }
-
private FaceStartUserClient getStartUserClient(@NonNull AidlResponseHandler resultController,
int sensorId, int newUserId, @NonNull FaceProvider provider) {
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index 0e2367a..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticationFrame;
-import android.hardware.face.FaceEnrollFrame;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-public class BiometricTestSessionImpl extends ITestSession.Stub {
- private static final String TAG = "BiometricTestSessionImpl";
-
- @NonNull private final Context mContext;
- private final int mSensorId;
- @NonNull private final ITestSessionCallback mCallback;
- @NonNull private final Face10 mFace10;
- @NonNull private final Face10.HalResultController mHalResultController;
- @NonNull private final Set<Integer> mEnrollmentIds;
- @NonNull private final Random mRandom;
-
-
- private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
- @Override
- public void onEnrollResult(Face face, int remaining) {
-
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
-
- }
-
- @Override
- public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onAuthenticationFailed() {
-
- }
-
- @Override
- public void onError(int error, int vendorCode) {
-
- }
-
- @Override
- public void onRemoved(Face face, int remaining) {
-
- }
-
- @Override
- public void onFeatureSet(boolean success, int feature) {
-
- }
-
- @Override
- public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
-
- }
-
- @Override
- public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
- }
-
- @Override
- public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
-
- }
-
- @Override
- public void onEnrollmentFrame(FaceEnrollFrame frame) {
-
- }
- };
-
- BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback,
- @NonNull Face10 face10,
- @NonNull Face10.HalResultController halResultController) {
- mContext = context;
- mSensorId = sensorId;
- mCallback = callback;
- mFace10 = face10;
- mHalResultController = halResultController;
- mEnrollmentIds = new HashSet<>();
- mRandom = new Random();
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void setTestHalEnabled(boolean enabled) {
-
- super.setTestHalEnabled_enforcePermission();
-
- mFace10.setTestHalEnabled(enabled);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void startEnroll(int userId) {
-
- super.startEnroll_enforcePermission();
-
- mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
- null /* previewSurface */, false /* debugConsent */,
- (new FaceEnrollOptions.Builder()).build());
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void finishEnroll(int userId) {
-
- super.finishEnroll_enforcePermission();
-
- int nextRandomId = mRandom.nextInt();
- while (mEnrollmentIds.contains(nextRandomId)) {
- nextRandomId = mRandom.nextInt();
- }
-
- mEnrollmentIds.add(nextRandomId);
- mHalResultController.onEnrollResult(0 /* deviceId */,
- nextRandomId /* faceId */, userId, 0);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void acceptAuthentication(int userId) {
-
- // Fake authentication with any of the existing fingers
- super.acceptAuthentication_enforcePermission();
-
- List<Face> faces = FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "No faces, returning");
- return;
- }
- final int fid = faces.get(0).getBiometricId();
- final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
- mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void rejectAuthentication(int userId) {
-
- super.rejectAuthentication_enforcePermission();
-
- mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
-
- super.notifyAcquired_enforcePermission();
-
- mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyError(int userId, int errorCode) {
-
- super.notifyError_enforcePermission();
-
- mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void cleanupInternalState(int userId) {
-
- super.cleanupInternalState_enforcePermission();
-
- mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- try {
- mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- try {
- mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- });
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
deleted file mode 100644
index 306ddfa..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ /dev/null
@@ -1,1342 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
-import android.hardware.face.Face;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.Looper;
-import android.os.NativeHandle;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-import android.view.Surface;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
-import com.android.server.biometrics.AuthenticationStatsCollector;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.SensorServiceStateProto;
-import com.android.server.biometrics.SensorStateProto;
-import com.android.server.biometrics.UserStateProto;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
-import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.face.FaceUtils;
-import com.android.server.biometrics.sensors.face.LockoutHalImpl;
-import com.android.server.biometrics.sensors.face.ServiceProvider;
-import com.android.server.biometrics.sensors.face.UsageStats;
-import com.android.server.biometrics.sensors.face.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.face.aidl.AidlSession;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.Clock;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
- * minor versions.
- */
-public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
-
- private static final String TAG = "Face10";
-
- private static final int ENROLL_TIMEOUT_SEC = 75;
- private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
- private static final int GENERATE_CHALLENGE_COUNTER_TTL_MILLIS =
- FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC * 1000;
- @VisibleForTesting
- public static Clock sSystemClock = Clock.systemUTC();
-
- private boolean mTestHalEnabled;
-
- @NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull
- private final AuthenticationStateListeners mAuthenticationStateListeners;
- @NonNull private final Context mContext;
- @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
- @NonNull private final Handler mHandler;
- @NonNull private final Supplier<IBiometricsFace> mLazyDaemon;
- @NonNull private final LockoutHalImpl mLockoutTracker;
- @NonNull private final UsageStats mUsageStats;
- @NonNull private final Map<Integer, Long> mAuthenticatorIds;
- @Nullable private IBiometricsFace mDaemon;
- @NonNull private final HalResultController mHalResultController;
- @NonNull private final BiometricContext mBiometricContext;
- @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
- // for requests that do not use biometric prompt
- @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
- private int mCurrentUserId = UserHandle.USER_NULL;
- private final int mSensorId;
- private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
- private final LockoutResetDispatcher mLockoutResetDispatcher;
- private FaceGenerateChallengeClient mGeneratedChallengeCache = null;
- private AidlSession mSession;
-
- private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(newUserId, null /* callback */);
- scheduleGetFeature(mSensorId, new Binder(), newUserId,
- BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION,
- null, mContext.getOpPackageName());
- }
- };
-
- public static class HalResultController extends IBiometricsFaceClientCallback.Stub {
- /**
- * Interface to sends results to the HalResultController's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- private final int mSensorId;
- @NonNull private final Context mContext;
- @NonNull private final Handler mHandler;
- @NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
- @Nullable private Callback mCallback;
- @NonNull private final LockoutHalImpl mLockoutTracker;
- @NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
-
-
- HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
- @NonNull LockoutHalImpl lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- mSensorId = sensorId;
- mContext = context;
- mHandler = handler;
- mScheduler = scheduler;
- mLockoutTracker = lockoutTracker;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- }
-
- public void setCallback(@Nullable Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onEnrollResult(long deviceId, int faceId, int userId, int remaining) {
- mHandler.post(() -> {
- final CharSequence name = FaceUtils.getLegacyInstance(mSensorId)
- .getUniqueName(mContext, userId);
- final Face face = new Face(name, faceId, deviceId);
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FaceEnrollClient)) {
- Slog.e(TAG, "onEnrollResult for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final FaceEnrollClient enrollClient = (FaceEnrollClient) client;
- enrollClient.onEnrollResult(face, remaining);
- });
- }
-
- @Override
- public void onAuthenticated(long deviceId, int faceId, int userId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final boolean authenticated = faceId != 0;
- final Face face = new Face("", faceId, deviceId);
- authenticationConsumer.onAuthenticated(face, authenticated, token);
- });
- }
-
- @Override
- public void onAcquired(long deviceId, int userId, int acquiredInfo,
- int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(TAG, "onAcquired for non-acquire client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient =
- (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onError(long deviceId, int userId, int error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(TAG, "handleError"
- + ", client: " + (client != null ? client.getOwnerString() : null)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(
- client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
- if (mCallback != null) {
- mCallback.onHardwareUnavailable();
- }
- }
- });
- }
-
- @Override
- public void onRemoved(long deviceId, ArrayList<Integer> removed, int userId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(TAG, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
-
- if (!removed.isEmpty()) {
- // Convert to old fingerprint-like behavior, where remove() receives
- // one removal at a time. This way, remove can share some more common code.
- for (int i = 0; i < removed.size(); i++) {
- final int id = removed.get(i);
- final Face face = new Face("", id, deviceId);
- final int remaining = removed.size() - i - 1;
- Slog.d(TAG, "Removed, faceId: " + id + ", remaining: " + remaining);
- removalConsumer.onRemoved(face, remaining);
- }
- } else {
- removalConsumer.onRemoved(null, 0 /* remaining */);
- }
-
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0, UserHandle.USER_CURRENT);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, ArrayList<Integer> faceIds, int userId) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
-
- if (!faceIds.isEmpty()) {
- // Convert to old fingerprint-like behavior, where enumerate() receives one
- // template at a time. This way, enumerate can share some more common code.
- for (int i = 0; i < faceIds.size(); i++) {
- final Face face = new Face("", faceIds.get(i), deviceId);
- enumerateConsumer.onEnumerationResult(face, faceIds.size() - i - 1);
- }
- } else {
- // For face, the HIDL contract is to receive an empty list when there are no
- // templates enrolled. Send a null identifier since we don't consume them
- // anywhere, and send remaining == 0 so this code can be shared with Face@1.1
- enumerateConsumer.onEnumerationResult(null /* identifier */, 0);
- }
- });
- }
-
- @Override
- public void onLockoutChanged(long duration) {
- mHandler.post(() -> {
- Slog.d(TAG, "onLockoutChanged: " + duration);
- final @LockoutTracker.LockoutMode int lockoutMode;
- if (duration == 0) {
- lockoutMode = LockoutTracker.LOCKOUT_NONE;
- } else if (duration == -1 || duration == Long.MAX_VALUE) {
- lockoutMode = LockoutTracker.LOCKOUT_PERMANENT;
- } else {
- lockoutMode = LockoutTracker.LOCKOUT_TIMED;
- }
-
- mLockoutTracker.setCurrentUserLockoutMode(lockoutMode);
-
- if (duration == 0) {
- mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorId);
- }
- });
- }
- }
-
- @VisibleForTesting
- Face10(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FaceSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFace, AidlSession> scheduler,
- @NonNull BiometricContext biometricContext) {
- mSensorProperties = sensorProps;
- mContext = context;
- mBiometricStateCallback = biometricStateCallback;
- mAuthenticationStateListeners = authenticationStateListeners;
- mSensorId = sensorProps.sensorId;
- mScheduler = scheduler;
- mHandler = handler;
- mBiometricContext = biometricContext;
- mUsageStats = new UsageStats(context);
- mAuthenticatorIds = new HashMap<>();
- mLazyDaemon = Face10.this::getDaemon;
- mLockoutTracker = new LockoutHalImpl();
- mHalResultController = new HalResultController(sensorProps.sensorId, context, mHandler,
- mScheduler, mLockoutTracker, lockoutResetDispatcher);
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mHalResultController.setCallback(() -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- });
-
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FACE,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(TAG, "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to register user switch observer");
- }
- }
-
- public static Face10 newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FaceSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, biometricStateCallback, authenticationStateListeners,
- sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityTracker */),
- BiometricContext.getInstance(context));
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(TAG, "HAL died");
- mHandler.post(() -> {
- PerformanceTracker.getInstanceForSensorId(mSensorId)
- .incrementHALDeathCount();
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (client instanceof ErrorConsumer) {
- Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- BiometricsProtoEnums.MODALITY_FACE,
- BiometricsProtoEnums.ISSUE_HAL_DEATH,
- -1 /* sensorId */);
- }
-
- mScheduler.recordCrashState();
- mScheduler.reset();
- });
- }
-
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- synchronized AidlSession getSession() {
- if (mDaemon != null && mSession != null) {
- return mSession;
- } else {
- return mSession = new AidlSession(mContext, this::getDaemon, mCurrentUserId,
- new AidlResponseHandler(mContext, mScheduler, mSensorId,
- mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
- new AuthSessionCoordinator(), () -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- }));
- }
- }
-
- private synchronized IBiometricsFace getDaemon() {
- if (mTestHalEnabled) {
- final TestHal testHal = new TestHal(mContext, mSensorId);
- testHal.setCallback(mHalResultController);
- return testHal;
- }
-
- if (mDaemon != null) {
- return mDaemon;
- }
-
- Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
- + mScheduler.getCurrentClient());
-
- try {
- mDaemon = IBiometricsFace.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened.
- Slog.w(TAG, "NoSuchElementException", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get face HAL", e);
- }
-
- if (mDaemon == null) {
- Slog.w(TAG, "Face HAL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
- // HAL ID for these HIDL versions are only used to determine if callbacks have been
- // successfully set.
- long halId = 0;
- try {
- halId = mDaemon.setCallback(mHalResultController).value;
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set callback for face HAL", e);
- mDaemon = null;
- }
-
- Slog.d(TAG, "Face HAL ready, HAL ID: " + halId);
- if (halId != 0) {
- scheduleLoadAuthenticatorIds();
- scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
- scheduleGetFeature(mSensorId, new Binder(),
- ActivityManager.getCurrentUser(),
- BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION, null,
- mContext.getOpPackageName());
- } else {
- Slog.e(TAG, "Unable to set callback");
- mDaemon = null;
- }
-
- return mDaemon;
- }
-
- @Override
- public boolean containsSensor(int sensorId) {
- return mSensorId == sensorId;
- }
-
- @Override
- @NonNull
- public List<FaceSensorPropertiesInternal> getSensorProperties() {
- final List<FaceSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @NonNull
- @Override
- public FaceSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensorProperties;
- }
-
- @Override
- @NonNull
- public List<Face> getEnrolledFaces(int sensorId, int userId) {
- return FaceUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
- }
-
- @Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFaces(sensorId, userId).isEmpty();
- }
-
- @Override
- @LockoutTracker.LockoutMode
- public int getLockoutModeForUser(int sensorId, int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- public long getAuthenticatorId(int sensorId, int userId) {
- return mAuthenticatorIds.getOrDefault(userId, 0L);
- }
-
- @Override
- public boolean isHardwareDetected(int sensorId) {
- return getDaemon() != null;
- }
-
- private boolean isGeneratedChallengeCacheValid() {
- return mGeneratedChallengeCache != null
- && sSystemClock.millis() - mGeneratedChallengeCache.getCreatedAt()
- < GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS;
- }
-
- private void incrementChallengeCount() {
- mGeneratedChallengeCount.add(0, sSystemClock.millis());
- }
-
- private int decrementChallengeCount() {
- final long now = sSystemClock.millis();
- // ignore values that are old in case generate/revoke calls are not matched
- // this doesn't ensure revoke if calls are mismatched but it keeps the list from growing
- mGeneratedChallengeCount.removeIf(x -> now - x > GENERATE_CHALLENGE_COUNTER_TTL_MILLIS);
- if (!mGeneratedChallengeCount.isEmpty()) {
- mGeneratedChallengeCount.remove(0);
- }
- return mGeneratedChallengeCount.size();
- }
-
- /**
- * {@link IBiometricsFace} only supports a single in-flight challenge but there are cases where
- * two callers both need challenges (e.g. resetLockout right before enrollment).
- */
- @Override
- public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
- } else {
- scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
- }
- });
- }
-
- private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceGenerateChallengeClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- if (client != clientMonitor) {
- Slog.e(TAG,
- "scheduleGenerateChallenge onClientStarted, mismatched client."
- + " Expecting: " + client + ", received: "
- + clientMonitor);
- }
- }
- });
- }
-
- private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- incrementChallengeCount();
- if (isGeneratedChallengeCacheValid()) {
- Slog.d(TAG, "Current challenge is cached and will be reused");
- mGeneratedChallengeCache.reuseResult(receiver);
- return;
- }
-
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
- opPackageName, mSensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, sSystemClock.millis());
- mGeneratedChallengeCache = client;
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- if (client != clientMonitor) {
- Slog.e(TAG,
- "scheduleGenerateChallenge onClientStarted, mismatched client."
- + " Expecting: " + client + ", received: "
- + clientMonitor);
- }
- }
- });
- }
-
- @Override
- public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull String opPackageName, long challenge) {
- mHandler.post(() -> {
- if (Flags.deHidl()) {
- scheduleRevokeChallengeAidl(userId, token, opPackageName);
- } else {
- scheduleRevokeChallengeHidl(userId, token, opPackageName);
- }
- });
- }
-
- private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
- @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient
- client =
- new com.android.server.biometrics.sensors.face.aidl.FaceRevokeChallengeClient(
- mContext, this::getSession, token, userId, opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector), mBiometricContext, 0L);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (client != clientMonitor) {
- Slog.e(TAG,
- "scheduleRevokeChallenge, mismatched client." + "Expecting: "
- + client + ", received: " + clientMonitor);
- }
- }
- });
- }
-
- private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
- @NonNull String opPackageName) {
- final boolean shouldRevoke = decrementChallengeCount() == 0;
- if (!shouldRevoke) {
- Slog.w(TAG, "scheduleRevokeChallenge skipped - challenge still in use: "
- + mGeneratedChallengeCount);
- return;
- }
-
- Slog.d(TAG, "scheduleRevokeChallenge executing - no active clients");
- mGeneratedChallengeCache = null;
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mLazyDaemon, token, userId, opPackageName, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (client != clientMonitor) {
- Slog.e(TAG,
- "scheduleRevokeChallenge, mismatched client." + "Expecting: "
- + client + ", received: " + clientMonitor);
- }
- }
- });
- }
-
- @Override
- public long scheduleEnroll(int sensorId, @NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
- @NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, boolean debugConsent,
- @NonNull FaceEnrollOptions options) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
- if (Flags.deHidl()) {
- scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id, options);
- } else {
- scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id, options);
- }
- });
- return id;
- }
-
- private void scheduleEnrollAidl(@NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
- @NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, long id,
- @NonNull FaceEnrollOptions options) {
- final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), userId,
- hardwareAuthToken, opPackageName, id,
- FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector), mBiometricContext,
- mContext.getResources().getInteger(
- com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
- false, options);
-
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
- }
- }
- });
- }
-
- private void scheduleEnrollHidl(@NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
- @NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, long id, FaceEnrollOptions options) {
- final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, options);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
- }
- }
- });
- }
-
- @Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
- }
-
- @Override
- public long scheduleFaceDetect(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter callback,
- @NonNull FaceAuthenticateOptions options, int statsClient) {
- throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
- + "forget to check the supportsFaceDetection flag?");
- }
-
- @Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
- throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
- + "forget to check the supportsFaceDetection flag?");
- }
-
- @Override
- public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
- int statsClient, boolean allowBackgroundAuthentication) {
- mHandler.post(() -> {
- final int userId = options.getUserId();
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
- if (Flags.deHidl()) {
- scheduleAuthenticateAidl(token, operationId, cookie, receiver, options, requestId,
- restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
- } else {
- scheduleAuthenticateHidl(token, operationId, cookie, receiver, options, requestId,
- restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
- }
- });
- }
-
- private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
- int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
- final com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient
- client =
- new com.android.server.biometrics.sensors.face.aidl.FaceAuthenticationClient(
- mContext, this::getSession, token, requestId, receiver, operationId,
- restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector), mBiometricContext,
- isStrongBiometric, mUsageStats, mLockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
- mAuthenticationStateListeners);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, long requestId, boolean restricted,
- int statsClient, boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
- final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, requestId, receiver, operationId, restricted, options,
- cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector), mBiometricContext,
- isStrongBiometric, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
- mAuthenticationStateListeners);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull FaceAuthenticateOptions options, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- final long id = mRequestCounter.incrementAndGet();
-
- scheduleAuthenticate(token, operationId, cookie, receiver,
- options, id, restricted, statsClient, allowBackgroundAuthentication);
-
- return id;
- }
-
- @Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
- }
-
- @Override
- public void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, userId, receiver, opPackageName, faceId);
- } else {
- scheduleRemoveHidl(token, userId, receiver, opPackageName, faceId);
- }
- });
- }
-
- @Override
- public void scheduleRemoveAll(int sensorId, @NonNull IBinder token, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- // For IBiometricsFace@1.0, remove(0) means remove all enrollments
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, userId, receiver, opPackageName, 0);
- } else {
- scheduleRemoveHidl(token, userId, receiver, opPackageName, 0);
- }
- });
- }
-
- private void scheduleRemoveAidl(@NonNull IBinder token, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
- final com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceRemovalClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), new int[]{faceId}, userId,
- opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector), mBiometricContext,
- mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- private void scheduleRemoveHidl(@NonNull IBinder token, int userId,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName, int faceId) {
- final FaceRemovalClient client = new FaceRemovalClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), faceId, userId,
- opPackageName, FaceUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- @Override
- public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
- mHandler.post(() -> {
- if (getEnrolledFaces(sensorId, userId).isEmpty()) {
- Slog.w(TAG, "Ignoring lockout reset, no templates enrolled for user: " + userId);
- return;
- }
-
- scheduleUpdateActiveUserWithoutHandler(userId);
- if (Flags.deHidl()) {
- scheduleResetLockoutAidl(userId, hardwareAuthToken);
- } else {
- scheduleResetLockoutHidl(userId, hardwareAuthToken);
- }
- });
- }
-
- private void scheduleResetLockoutAidl(int userId,
- @NonNull byte[] hardwareAuthToken) {
- final com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceResetLockoutClient(
- mContext, this::getSession, userId, mContext.getOpPackageName(),
- mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, hardwareAuthToken, mLockoutTracker,
- mLockoutResetDispatcher, mSensorProperties.sensorStrength);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleResetLockoutHidl(int userId,
- @NonNull byte[] hardwareAuthToken) {
- final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
- mLazyDaemon,
- userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, hardwareAuthToken);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public void scheduleSetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
- boolean enabled, @NonNull byte[] hardwareAuthToken,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
- if (Flags.deHidl()) {
- scheduleSetFeatureAidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
- receiver, opPackageName);
- } else {
- scheduleSetFeatureHidl(sensorId, token, userId, feature, enabled, hardwareAuthToken,
- receiver, opPackageName);
- }
- });
- }
-
- private void scheduleSetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
- int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring setFeature, no templates enrolled for user: " + userId);
- return;
- }
- final int faceId = faces.get(0).getBiometricId();
- final FaceSetFeatureClient client = new FaceSetFeatureClient(mContext, mLazyDaemon,
- token, new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext, feature,
- enabled, hardwareAuthToken, faceId);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleSetFeatureAidl(int sensorId, @NonNull IBinder token, int userId,
- int feature, boolean enabled, @NonNull byte[] hardwareAuthToken,
- @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceSetFeatureClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
- feature, enabled, hardwareAuthToken);
- mScheduler.scheduleClientMonitor(client);
- }
-
-
- @Override
- public void scheduleGetFeature(int sensorId, @NonNull IBinder token, int userId, int feature,
- @Nullable ClientMonitorCallbackConverter listener, @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleGetFeatureAidl(token, userId, feature, listener,
- opPackageName);
- } else {
- scheduleGetFeatureHidl(sensorId, token, userId, feature, listener,
- opPackageName);
- }
- });
- }
-
- private void scheduleGetFeatureHidl(int sensorId, @NonNull IBinder token, int userId,
- int feature, @Nullable ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName) {
- final List<Face> faces = getEnrolledFaces(sensorId, userId);
- if (faces.isEmpty()) {
- Slog.w(TAG, "Ignoring getFeature, no templates enrolled for user: " + userId);
- return;
- }
-
- final int faceId = faces.get(0).getBiometricId();
- final FaceGetFeatureClient client = new FaceGetFeatureClient(mContext, mLazyDaemon,
- token, listener, userId, opPackageName, mSensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext, feature, faceId);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success
- && feature == BiometricFaceConstants.FEATURE_REQUIRE_ATTENTION) {
- final int settingsValue = client.getValue() ? 1 : 0;
- Slog.d(TAG,
- "Updating attention value for user: " + userId + " to value: "
- + settingsValue);
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED, settingsValue,
- userId);
- }
- }
- });
- }
-
- private void scheduleGetFeatureAidl(@NonNull IBinder token, int userId,
- int feature, @Nullable ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient client =
- new com.android.server.biometrics.sensors.face.aidl.FaceGetFeatureClient(
- mContext, this::getSession, token, listener, userId, opPackageName,
- mSensorId, BiometricLogger.ofUnknown(mContext), mBiometricContext,
- feature);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleInternalCleanup(int userId,
- @Nullable ClientMonitorCallback callback) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
- if (Flags.deHidl()) {
- scheduleInternalCleanupAidl(userId, callback);
- } else {
- scheduleInternalCleanupHidl(userId, callback);
- }
- });
- }
-
- private void scheduleInternalCleanupHidl(int userId,
- @Nullable ClientMonitorCallback callback) {
- final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
- mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
- mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client,
- new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
- }
-
- private void scheduleInternalCleanupAidl(int userId,
- @Nullable ClientMonitorCallback callback) {
- final com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient
- client =
- new com.android.server.biometrics.sensors.face.aidl.FaceInternalCleanupClient(
- mContext, this::getSession, userId, mContext.getOpPackageName(),
- mSensorId, createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, FaceUtils.getLegacyInstance(mSensorId),
- mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client,
- new ClientMonitorCompositeCallback(callback, mBiometricStateCallback));
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback) {
- scheduleInternalCleanup(userId, mBiometricStateCallback);
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
- scheduleInternalCleanup(userId, callback);
- }
-
- @Override
- public void startPreparedClient(int sensorId, int cookie) {
- mHandler.post(() -> {
- mScheduler.startPreparedClient(cookie);
- });
- }
-
- @Override
- public void dumpProtoState(int sensorId, ProtoOutputStream proto,
- boolean clearSchedulerBuffer) {
- final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
- proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
- proto.write(SensorStateProto.MODALITY, SensorStateProto.FACE);
- proto.write(SensorStateProto.CURRENT_STRENGTH,
- Utils.getCurrentStrength(mSensorProperties.sensorId));
- proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(SensorStateProto.USER_STATES);
- proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED, FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
- proto.end(userToken);
- }
-
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
- mSensorProperties.resetLockoutRequiresHardwareAuthToken);
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
- mSensorProperties.resetLockoutRequiresChallenge);
-
- proto.end(sensorToken);
- }
-
- @Override
- public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
- }
-
- @Override
- public void dumpInternal(int sensorId, PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(mSensorId);
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", TAG);
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int c = FaceUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", c);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure face transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
-
- mScheduler.dump(pw);
- mUsageStats.print(pw);
- }
-
- private void scheduleLoadAuthenticatorIds() {
- // Note that this can be performed on the scheduler (as opposed to being done immediately
- // when the HAL is (re)loaded, since
- // 1) If this is truly the first time it's being performed (e.g. system has just started),
- // this will be run very early and way before any applications need to generate keys.
- // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
- // just been reloaded), the framework already has a cache of the authenticatorIds. This
- // is safe because authenticatorIds only change when A) new template has been enrolled,
- // or B) all templates are removed.
- mHandler.post(() -> {
- for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
- final int targetUserId = user.id;
- if (!mAuthenticatorIds.containsKey(targetUserId)) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId);
- }
- }
- });
- }
-
- /**
- * Schedules the {@link FaceUpdateActiveUserClient} without posting the work onto the handler.
- * Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
- * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
- * this operation on the same lambda/runnable as those operations so that the ordering is
- * correct.
- */
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
- final boolean hasEnrolled = !getEnrolledFaces(mSensorId, targetUserId).isEmpty();
- final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
- mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, hasEnrolled, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- if (mCurrentUserId != targetUserId) {
- // Create new session with updated user ID
- mSession = null;
- }
- mCurrentUserId = targetUserId;
- } else {
- Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
- }
- }
- });
- }
-
- private BiometricLogger createLogger(int statsAction, int statsClient,
- AuthenticationStatsCollector authenticationStatsCollector) {
- return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient, authenticationStatsCollector);
- }
-
- /**
- * Sends a debug message to the HAL with the provided FileDescriptor and arguments.
- */
- public void dumpHal(int sensorId, @NonNull FileDescriptor fd, @NonNull String[] args) {
- // WARNING: CDD restricts image data from leaving TEE unencrypted on
- // production devices:
- // [C-1-10] MUST not allow unencrypted access to identifiable biometric
- // data or any data derived from it (such as embeddings) to the
- // Application Processor outside the context of the TEE.
- // As such, this API should only be enabled for testing purposes on
- // engineering and userdebug builds. All modules in the software stack
- // MUST enforce final build products do NOT have this functionality.
- // Additionally, the following check MUST NOT be removed.
- if (!(Build.IS_ENG || Build.IS_USERDEBUG)) {
- return;
- }
-
- // Additionally, this flag allows turning off face for a device
- // (either permanently through the build or on an individual device).
- if (SystemProperties.getBoolean("ro.face.disable_debug_data", false)
- || SystemProperties.getBoolean("persist.face.disable_debug_data", false)) {
- return;
- }
-
- // The debug method takes two file descriptors. The first is for text
- // output, which we will drop. The second is for binary data, which
- // will be the protobuf data.
- final IBiometricsFace daemon = getDaemon();
- if (daemon != null) {
- FileOutputStream devnull = null;
- try {
- devnull = new FileOutputStream("/dev/null");
- final NativeHandle handle = new NativeHandle(
- new FileDescriptor[]{devnull.getFD(), fd},
- new int[0], false);
- daemon.debug(handle, new ArrayList<String>(Arrays.asList(args)));
- } catch (IOException | RemoteException ex) {
- Slog.d(TAG, "error while reading face debugging data", ex);
- } finally {
- if (devnull != null) {
- try {
- devnull.close();
- } catch (IOException ex) {
- }
- }
- }
- }
- }
-
- void setTestHalEnabled(boolean enabled) {
- mTestHalEnabled = enabled;
- }
-
- @NonNull
- @Override
- public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorId, callback,
- this, mHalResultController);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
deleted file mode 100644
index e44b263..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.SensorPrivacyManager;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.FaceAuthenticateOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.face.UsageStats;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific authentication client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceAuthenticationClient
- extends AuthenticationClient<IBiometricsFace, FaceAuthenticateOptions> {
-
- private static final String TAG = "FaceAuthenticationClient";
-
- private final UsageStats mUsageStats;
-
- private final int[] mBiometricPromptIgnoreList;
- private final int[] mBiometricPromptIgnoreListVendor;
- private final int[] mKeyguardIgnoreList;
- private final int[] mKeyguardIgnoreListVendor;
-
- private int mLastAcquire;
- private SensorPrivacyManager mSensorPrivacyManager;
- @NonNull
- private final AuthenticationStateListeners mAuthenticationStateListeners;
-
- FaceAuthenticationClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, long operationId,
- boolean restricted, @NonNull FaceAuthenticateOptions options, int cookie,
- boolean requireConfirmation,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
- @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength,
- @NonNull AuthenticationStateListeners authenticationStateListeners) {
- super(context, lazyDaemon, token, listener, operationId, restricted,
- options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, null /* taskStackListener */,
- lockoutTracker, allowBackgroundAuthentication, false /* shouldVibrate */,
- sensorStrength);
- setRequestId(requestId);
- mUsageStats = usageStats;
- mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
- mAuthenticationStateListeners = authenticationStateListeners;
-
- final Resources resources = getContext().getResources();
- mBiometricPromptIgnoreList = resources.getIntArray(
- R.array.config_face_acquire_biometricprompt_ignorelist);
- mBiometricPromptIgnoreListVendor = resources.getIntArray(
- R.array.config_face_acquire_vendor_biometricprompt_ignorelist);
- mKeyguardIgnoreList = resources.getIntArray(
- R.array.config_face_acquire_keyguard_ignorelist);
- mKeyguardIgnoreListVendor = resources.getIntArray(
- R.array.config_face_acquire_vendor_keyguard_ignorelist);
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- mState = STATE_STARTED;
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected void startHalOperation() {
-
- if (mSensorPrivacyManager != null
- && mSensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
- SensorPrivacyManager.Sensors.CAMERA)) {
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- return;
- }
-
- try {
- getFreshDaemon().authenticate(mOperationId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public boolean wasUserDetected() {
- // Do not provide haptic feedback if the user was not detected, and an error (usually
- // ERROR_TIMEOUT) is received.
- return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
- && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
- }
-
- @Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
- // For face, the authentication lifecycle ends either when
- // 1) Authenticated == true
- // 2) Error occurred
- // 3) Authenticated == false
- mCallback.onClientFinished(this, true /* success */);
- }
-
- @Override
- public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(userId);
- final PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
- if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
- performanceTracker.incrementPermanentLockoutForUser(userId);
- } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
- performanceTracker.incrementTimedLockoutForUser(userId);
- }
-
- return lockoutMode;
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
-
- mState = STATE_STOPPED;
- mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
- getStartTimeMs(),
- System.currentTimeMillis() - getStartTimeMs() /* latency */,
- authenticated,
- 0 /* error */,
- 0 /* vendorError */,
- getTargetUserId()));
-
- if (reportBiometricAuthAttempts()) {
- if (authenticated) {
- mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
- getTargetUserId());
- } else {
- mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
- getTargetUserId());
- }
- }
- }
-
- @Override
- public void onError(@BiometricConstants.Errors int error, int vendorCode) {
- mUsageStats.addEvent(new UsageStats.AuthenticationEvent(
- getStartTimeMs(),
- System.currentTimeMillis() - getStartTimeMs() /* latency */,
- false /* authenticated */,
- error,
- vendorCode,
- getTargetUserId()));
-
- super.onError(error, vendorCode);
- }
-
- private int[] getAcquireIgnorelist() {
- return isBiometricPrompt() ? mBiometricPromptIgnoreList : mKeyguardIgnoreList;
- }
-
- private int[] getAcquireVendorIgnorelist() {
- return isBiometricPrompt() ? mBiometricPromptIgnoreListVendor : mKeyguardIgnoreListVendor;
- }
-
- private boolean shouldSend(int acquireInfo, int vendorCode) {
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- return !Utils.listContains(getAcquireVendorIgnorelist(), vendorCode);
- } else {
- return !Utils.listContains(getAcquireIgnorelist(), acquireInfo);
- }
- }
-
- @Override
- public void onAcquired(int acquireInfo, int vendorCode) {
- mLastAcquire = acquireInfo;
-
- if (acquireInfo == FaceManager.FACE_ACQUIRED_RECALIBRATE) {
- BiometricNotificationUtils.showReEnrollmentNotification(getContext());
- }
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(getTargetUserId());
- if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
- }
-
- final boolean shouldSend = shouldSend(acquireInfo, vendorCode);
- onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
deleted file mode 100644
index 815cf91..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.hardware.face.Face;
-import android.hardware.face.FaceEnrollOptions;
-import android.hardware.face.FaceManager;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.view.Surface;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnrollClient;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Supplier;
-
-/**
- * Face-specific enroll client supporting the {@link android.hardware.biometrics.face.V1_0} HIDL
- * interface.
- */
-public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
-
- private static final String TAG = "FaceEnrollClient";
-
- @NonNull private final int[] mDisabledFeatures;
- @NonNull private final int[] mEnrollIgnoreList;
- @NonNull private final int[] mEnrollIgnoreListVendor;
-
- FaceEnrollClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
- @NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
- @Nullable Surface previewSurface, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull FaceEnrollOptions options) {
- super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
- BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
- setRequestId(requestId);
- mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
- mEnrollIgnoreList = getContext().getResources()
- .getIntArray(R.array.config_face_acquire_enroll_ignorelist);
- mEnrollIgnoreListVendor = getContext().getResources()
- .getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
-
- Slog.w(TAG, "EnrollOptions "
- + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason()));
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- BiometricNotificationUtils.cancelFaceEnrollNotification(getContext());
- BiometricNotificationUtils.cancelFaceReEnrollNotification(getContext());
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit() {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_faceMaxTemplatesPerUser);
- final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
- .size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many faces registered, user: " + getTargetUserId());
- return true;
- }
- return false;
- }
-
- @Override
- public void onAcquired(int acquireInfo, int vendorCode) {
- final boolean shouldSend;
- if (acquireInfo == FaceManager.FACE_ACQUIRED_VENDOR) {
- shouldSend = !Utils.listContains(mEnrollIgnoreListVendor, vendorCode);
- } else {
- shouldSend = !Utils.listContains(mEnrollIgnoreList, acquireInfo);
- }
- onAcquiredInternal(acquireInfo, vendorCode, shouldSend);
- }
-
- @Override
- protected void startHalOperation() {
- final ArrayList<Byte> token = new ArrayList<>();
- for (byte b : mHardwareAuthToken) {
- token.add(b);
- }
- final ArrayList<Integer> disabledFeatures = new ArrayList<>();
- for (int disabledFeature : mDisabledFeatures) {
- disabledFeatures.add(disabledFeature);
- }
-
- try {
- final int status = getFreshDaemon().enroll(token, mTimeoutSec, disabledFeatures);
-
- if (status != Status.OK) {
- onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
deleted file mode 100644
index 97838a7..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClient.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific generateChallenge client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-public class FaceGenerateChallengeClient extends GenerateChallengeClient<IBiometricsFace> {
-
- private static final String TAG = "FaceGenerateChallengeClient";
- static final int CHALLENGE_TIMEOUT_SEC = 600; // 10 minutes
- private static final ClientMonitorCallback EMPTY_CALLBACK = new ClientMonitorCallback() {
- };
-
- private final long mCreatedAt;
- private List<IFaceServiceReceiver> mWaiting;
- private Long mChallengeResult;
-
- FaceGenerateChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, long now) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
- biometricContext);
- mCreatedAt = now;
- mWaiting = new ArrayList<>();
- }
-
- @Override
- protected void startHalOperation() {
- mChallengeResult = null;
- try {
- mChallengeResult = getFreshDaemon().generateChallenge(CHALLENGE_TIMEOUT_SEC).value;
- // send the result to the original caller via mCallback and any waiting callers
- // that called reuseResult
- sendChallengeResult(getListener(), mCallback);
- for (IFaceServiceReceiver receiver : mWaiting) {
- sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "generateChallenge failed", e);
- mCallback.onClientFinished(this, false /* success */);
- } finally {
- mWaiting = null;
- }
- }
-
- /** @return An arbitrary time value for caching provided to the constructor. */
- public long getCreatedAt() {
- return mCreatedAt;
- }
-
- /**
- * Reuse the result of this operation when it is available. The receiver will be notified
- * immediately if a challenge has already been generated.
- *
- * @param receiver receiver to be notified of challenge result
- */
- public void reuseResult(@NonNull IFaceServiceReceiver receiver) {
- if (mWaiting != null) {
- mWaiting.add(receiver);
- } else {
- sendChallengeResult(new ClientMonitorCallbackConverter(receiver), EMPTY_CALLBACK);
- }
- }
-
- private void sendChallengeResult(@NonNull ClientMonitorCallbackConverter receiver,
- @NonNull ClientMonitorCallback ownerCallback) {
- Preconditions.checkState(mChallengeResult != null, "result not available");
- try {
- receiver.onChallengeGenerated(getSensorId(), getTargetUserId(), mChallengeResult);
- ownerCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- ownerCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
deleted file mode 100644
index 47aaeec..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceGetFeatureClient.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.OptionalBool;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific getFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceGetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceGetFeatureClient";
-
- private final int mFeature;
- private final int mFaceId;
- private boolean mValue;
-
- FaceGetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @Nullable ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- int feature, int faceId) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
- mFeature = feature;
- mFaceId = faceId;
- }
-
- @Override
- public void unableToStart() {
- try {
- getListener().onFeatureGet(false /* success */, new int[0], new boolean[0]);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final OptionalBool result = getFreshDaemon().getFeature(mFeature, mFaceId);
- int[] features = new int[1];
- boolean[] featureState = new boolean[1];
- features[0] = mFeature;
- featureState[0] = result.value;
- mValue = result.value;
-
- getListener().onFeatureGet(result.status == Status.OK, features, featureState);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to getFeature", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- boolean getValue() {
- return mValue;
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_GET_FEATURE;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
deleted file mode 100644
index 89a17c6..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalCleanupClient.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalCleanupClient extends InternalCleanupClient<Face, IBiometricsFace> {
-
- FaceInternalCleanupClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull BiometricUtils<Face> utils, @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
- utils, authenticatorIds);
- }
-
- @Override
- protected InternalEnumerateClient<IBiometricsFace> getEnumerateClient(Context context,
- Supplier<IBiometricsFace> lazyDaemon, IBinder token, int userId, String owner,
- List<Face> enrolledList, BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- return new FaceInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId, logger, biometricContext);
- }
-
- @Override
- protected RemovalClient<Face, IBiometricsFace> getRemovalClient(Context context,
- Supplier<IBiometricsFace> lazyDaemon, IBinder token,
- int biometricId, int userId, String owner, BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- Map<Integer, Long> authenticatorIds) {
- // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
- // is all done internally.
- return new FaceRemovalClient(context, lazyDaemon, token,
- null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, logger, biometricContext, authenticatorIds);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
deleted file mode 100644
index 250dd7e..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceInternalEnumerateClient.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Face-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.face.V1_0} HIDL interface.
- */
-class FaceInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFace> {
- private static final String TAG = "FaceInternalEnumerateClient";
-
- FaceInternalEnumerateClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token, int userId,
- @NonNull String owner, @NonNull List<Face> enrolledList,
- @NonNull BiometricUtils<Face> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().enumerate();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enumerate", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
deleted file mode 100644
index 0ee7a35..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRemovalClient.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.face.Face;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Face-specific removal client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-class FaceRemovalClient extends RemovalClient<Face, IBiometricsFace> {
- private static final String TAG = "FaceRemovalClient";
-
- private final int mBiometricId;
-
- FaceRemovalClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
- int biometricId, int userId, @NonNull String owner, @NonNull BiometricUtils<Face> utils,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId, logger,
- biometricContext, authenticatorIds);
- mBiometricId = biometricId;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().remove(mBiometricId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting remove", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
deleted file mode 100644
index f29b9e8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceResetLockoutClient.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific resetLockout client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceResetLockoutClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceResetLockoutClient";
-
- private final ArrayList<Byte> mHardwareAuthToken;
-
- FaceResetLockoutClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, int userId, String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull byte[] hardwareAuthToken) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
-
- mHardwareAuthToken = new ArrayList<>();
- for (byte b : hardwareAuthToken) {
- mHardwareAuthToken.add(b);
- }
- }
-
- @Override
- public void unableToStart() {
- // Nothing to do here
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- public boolean interruptsPrecedingClients() {
- return true;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().resetLockout(mHardwareAuthToken);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to reset lockout", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_RESET_LOCKOUT;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
deleted file mode 100644
index b7b0dc04..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceRevokeChallengeClient.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Face-specific revokeChallenge client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceRevokeChallengeClient extends RevokeChallengeClient<IBiometricsFace> {
-
- private static final String TAG = "FaceRevokeChallengeClient";
-
- FaceRevokeChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().revokeChallenge();
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "revokeChallenge failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
deleted file mode 100644
index 3c82f9c..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceSetFeatureClient.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.Status;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Face-specific setFeature client supporting the {@link android.hardware.biometrics.face.V1_0}
- * HIDL interface.
- */
-public class FaceSetFeatureClient extends HalClientMonitor<IBiometricsFace> {
-
- private static final String TAG = "FaceSetFeatureClient";
-
- private final int mFeature;
- private final boolean mEnabled;
- private final ArrayList<Byte> mHardwareAuthToken;
- private final int mFaceId;
-
- FaceSetFeatureClient(@NonNull Context context, @NonNull Supplier<IBiometricsFace> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- int feature, boolean enabled, byte[] hardwareAuthToken, int faceId) {
- super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
- logger, biometricContext);
- mFeature = feature;
- mEnabled = enabled;
- mFaceId = faceId;
-
- mHardwareAuthToken = new ArrayList<>();
- for (byte b : hardwareAuthToken) {
- mHardwareAuthToken.add(b);
- }
- }
-
- @Override
- public void unableToStart() {
- try {
- getListener().onFeatureSet(false /* success */, mFeature);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to send error", e);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final int result = getFreshDaemon()
- .setFeature(mFeature, mEnabled, mHardwareAuthToken, mFaceId);
- getListener().onFeatureSet(result == Status.OK, mFeature);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to set feature: " + mFeature + " to enabled: " + mEnabled, e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_SET_FEATURE;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
index a004cae4..9a4c29d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapter.java
@@ -173,9 +173,14 @@
}
private AidlResponseHandler getAidlResponseHandler() {
- return new AidlResponseHandler(getContext(), getScheduler(), getSensorProperties().sensorId,
- mCurrentUserId, mLockoutTracker, mLockoutResetDispatcher,
- mAuthSessionCoordinator, () -> {}, mAidlResponseHandlerCallback);
+ return new AidlResponseHandler(getContext(),
+ getScheduler(),
+ getSensorProperties().sensorId,
+ mCurrentUserId,
+ mLockoutTracker,
+ mLockoutResetDispatcher,
+ mAuthSessionCoordinator,
+ mAidlResponseHandlerCallback);
}
private IBiometricsFace getIBiometricsFace() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
index fa95361..45d0cfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapter.java
@@ -53,7 +53,7 @@
private static final String TAG = "HidlToAidlSessionAdapter";
- private static final int CHALLENGE_TIMEOUT_SEC = 600;
+ @VisibleForTesting static final int CHALLENGE_TIMEOUT_SEC = 600;
@DurationMillisLong
private static final int GENERATE_CHALLENGE_REUSE_INTERVAL_MILLIS = 60 * 1000;
@DurationMillisLong
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index d762914..deda93c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -72,7 +72,6 @@
import android.os.ShellCallback;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
@@ -83,7 +82,6 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -94,12 +92,8 @@
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21;
-import com.android.server.biometrics.sensors.fingerprint.hidl.Fingerprint21UdfpsMock;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.google.android.collect.Lists;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -886,9 +880,9 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
- public void registerAuthenticatorsLegacy(
+ public void registerAuthenticators(
@NonNull FingerprintSensorConfigurations fingerprintSensorConfigurations) {
- super.registerAuthenticatorsLegacy_enforcePermission();
+ super.registerAuthenticators_enforcePermission();
if (!fingerprintSensorConfigurations.hasSensorConfigurations()) {
Slog.d(TAG, "No fingerprint sensors available.");
return;
@@ -897,30 +891,6 @@
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override // Binder call
- public void registerAuthenticators(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- super.registerAuthenticators_enforcePermission();
-
- mRegistry.registerAll(() -> {
- List<String> aidlSensors = new ArrayList<>();
- final String[] instances = mAidlInstanceNameSupplier.get();
- if (instances != null) {
- aidlSensors.addAll(Lists.newArrayList(instances));
- }
-
- final Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
- filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
-
- final List<ServiceProvider> providers = new ArrayList<>();
- providers.addAll(getHidlProviders(filteredInstances.first));
- providers.addAll(getAidlProviders(filteredInstances.second));
-
- return providers;
- });
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public void addAuthenticatorsRegisteredCallback(
IFingerprintAuthenticatorsRegisteredCallback callback) {
@@ -1086,22 +1056,15 @@
return null;
};
- if (Flags.deHidl()) {
- mFingerprintProviderFunction = fingerprintProviderFunction == null
- ? (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
- new FingerprintProvider(
- getContext(), mBiometricStateCallback,
- mAuthenticationStateListeners,
- filteredSensorProps.second,
- filteredSensorProps.first, mLockoutResetDispatcher,
- mGestureAvailabilityDispatcher,
- mBiometricContext,
- resetLockoutRequiresHardwareAuthToken)
- : fingerprintProviderFunction;
- } else {
- mFingerprintProviderFunction =
- (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) -> null;
- }
+ mFingerprintProviderFunction = fingerprintProviderFunction != null
+ ? fingerprintProviderFunction :
+ (filteredSensorProps, resetLockoutRequiresHardwareAuthToken) ->
+ new FingerprintProvider(getContext(), mBiometricStateCallback,
+ mAuthenticationStateListeners, filteredSensorProps.second,
+ filteredSensorProps.first, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher, mBiometricContext,
+ resetLockoutRequiresHardwareAuthToken);
+
mHandler = new Handler(Looper.getMainLooper());
mRegistry = new FingerprintServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1160,74 +1123,6 @@
.getSensorPropForInstance(finalSensorInstance));
}
- private Pair<List<FingerprintSensorPropertiesInternal>, List<String>>
- filterAvailableHalInstances(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlInstances,
- @NonNull List<String> aidlInstances) {
- if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
- return new Pair(hidlInstances, aidlInstances);
- }
-
- final int virtualAt = aidlInstances.indexOf("virtual");
- if (Utils.isFingerprintVirtualEnabled(getContext())) {
- if (virtualAt != -1) {
- //only virtual instance should be returned
- Slog.i(TAG, "virtual hal is used");
- return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
- } else {
- Slog.e(TAG, "Could not find virtual interface while it is enabled");
- return new Pair(hidlInstances, aidlInstances);
- }
- } else {
- //remove virtual instance
- aidlInstances = new ArrayList<>(aidlInstances);
- if (virtualAt != -1) {
- aidlInstances.remove(virtualAt);
- }
- return new Pair(hidlInstances, aidlInstances);
- }
- }
-
- @NonNull
- private List<ServiceProvider> getHidlProviders(
- @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (FingerprintSensorPropertiesInternal hidlSensor : hidlSensors) {
- final Fingerprint21 fingerprint21;
- if ((Build.IS_USERDEBUG || Build.IS_ENG)
- && getContext().getResources().getBoolean(R.bool.allow_test_udfps)
- && Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
- UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
- mBiometricStateCallback, mAuthenticationStateListeners,
- hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
- BiometricContext.getInstance(getContext()));
- } else {
- fingerprint21 = Fingerprint21.newInstance(getContext(),
- mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor,
- mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
- }
- providers.add(fingerprint21);
- }
-
- return providers;
- }
-
- @NonNull
- private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
- final List<ServiceProvider> providers = new ArrayList<>();
-
- for (String instance : instances) {
- final FingerprintProvider provider = mFingerprintProvider.apply(instance);
- Slog.i(TAG, "Adding AIDL provider: " + instance);
- providers.add(provider);
- }
-
- return providers;
- }
-
@Override
public void onStart() {
publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
index bd21cf4..6d1715f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/AidlResponseHandler.java
@@ -25,7 +25,6 @@
import android.hardware.keymaster.HardwareAuthToken;
import android.util.Slog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AcquisitionClient;
@@ -53,16 +52,6 @@
/**
* Interface to send results to the AidlResponseHandler's owner.
*/
- public interface HardwareUnavailableCallback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- /**
- * Interface to send results to the AidlResponseHandler's owner.
- */
public interface AidlResponseHandlerCallback {
/**
* Invoked when enrollment is successful.
@@ -90,8 +79,6 @@
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull
- private final HardwareUnavailableCallback mHardwareUnavailableCallback;
- @NonNull
private final AidlResponseHandlerCallback mAidlResponseHandlerCallback;
public AidlResponseHandler(@NonNull Context context,
@@ -99,24 +86,6 @@
@NonNull LockoutTracker lockoutTracker,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback) {
- this(context, scheduler, sensorId, userId, lockoutTracker, lockoutResetDispatcher,
- authSessionCoordinator, hardwareUnavailableCallback,
- new AidlResponseHandlerCallback() {
- @Override
- public void onEnrollSuccess() {}
-
- @Override
- public void onHardwareUnavailable() {}
- });
- }
-
- public AidlResponseHandler(@NonNull Context context,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
- @NonNull LockoutTracker lockoutTracker,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull AuthSessionCoordinator authSessionCoordinator,
- @NonNull HardwareUnavailableCallback hardwareUnavailableCallback,
@NonNull AidlResponseHandlerCallback aidlResponseHandlerCallback) {
mContext = context;
mScheduler = scheduler;
@@ -125,7 +94,6 @@
mLockoutTracker = lockoutTracker;
mLockoutResetDispatcher = lockoutResetDispatcher;
mAuthSessionCoordinator = authSessionCoordinator;
- mHardwareUnavailableCallback = hardwareUnavailableCallback;
mAidlResponseHandlerCallback = aidlResponseHandlerCallback;
}
@@ -171,11 +139,7 @@
handleResponse(ErrorConsumer.class, (c) -> {
c.onError(error, vendorCode);
if (error == Error.HW_UNAVAILABLE) {
- if (Flags.deHidl()) {
- mAidlResponseHandlerCallback.onHardwareUnavailable();
- } else {
- mHardwareUnavailableCallback.onHardwareUnavailable();
- }
+ mAidlResponseHandlerCallback.onHardwareUnavailable();
}
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 93d1b6e..5edcbed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -31,23 +31,16 @@
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Build;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Slog;
-import com.android.internal.R;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -67,7 +60,6 @@
import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import java.time.Clock;
import java.util.ArrayList;
import java.util.function.Supplier;
@@ -79,30 +71,17 @@
extends AuthenticationClient<AidlSession, FingerprintAuthenticateOptions>
implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
- private static final int MESSAGE_AUTH_SUCCESS = 2;
- private static final int MESSAGE_FINGER_UP = 3;
@NonNull
private final SensorOverlays mSensorOverlays;
@NonNull
private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull
private final CallbackWithProbe<Probe> mALSProbeCallback;
- private final Handler mHandler;
- private final int mSkipWaitForPowerAcquireMessage;
- private final int mSkipWaitForPowerVendorAcquireMessage;
- private final long mFingerUpIgnoresPower = 500;
private final AuthSessionCoordinator mAuthSessionCoordinator;
@NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
@Nullable
private ICancellationSignal mCancellationSignal;
private boolean mIsPointerDown;
- private long mWaitForAuthKeyguard;
- private long mWaitForAuthBp;
- private long mIgnoreAuthFor;
- private long mSideFpsLastAcquireStartTime;
- private Runnable mAuthSuccessRunnable;
- private final Clock mClock;
-
public FingerprintAuthenticationClient(
@NonNull Context context,
@@ -125,9 +104,7 @@
@NonNull AuthenticationStateListeners authenticationStateListeners,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull Handler handler,
@Authenticators.Types int biometricStrength,
- @NonNull Clock clock,
@Nullable LockoutTracker lockoutTracker) {
super(
context,
@@ -156,39 +133,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
- mHandler = handler;
-
- mWaitForAuthKeyguard =
- context.getResources()
- .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
- mWaitForAuthBp =
- context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
- mIgnoreAuthFor =
- context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
-
- mSkipWaitForPowerAcquireMessage =
- context.getResources().getInteger(
- R.integer.config_sidefpsSkipWaitForPowerAcquireMessage);
- mSkipWaitForPowerVendorAcquireMessage =
- context.getResources().getInteger(
- R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage);
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
- mSideFpsLastAcquireStartTime = -1;
- mClock = clock;
-
- if (mSensorProps.isAnySidefpsType()) {
- if (Build.isDebuggable()) {
- mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
- (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
- mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
- UserHandle.USER_CURRENT);
- mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
- UserHandle.USER_CURRENT);
- }
- }
}
@Override
@@ -316,11 +261,7 @@
}
try {
- if (Flags.deHidl()) {
- startAuthentication();
- } else {
- mCancellationSignal = doAuthenticate();
- }
+ doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
onError(
@@ -334,49 +275,7 @@
}
}
- private ICancellationSignal doAuthenticate() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel;
- if (session.hasContextMethods()) {
- cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext(getOptions()));
- } else {
- cancel = session.getSession().authenticate(mOperationId);
- }
-
- getBiometricContext().subscribe(opContext, ctx -> {
- if (session.hasContextMethods()) {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- }
-
- // TODO(b/243836005): this should come via ctx
- final boolean isAwake = getBiometricContext().isAwake();
- if (isAwake) {
- mALSProbeCallback.getProbe().enable();
- } else {
- mALSProbeCallback.getProbe().disable();
- }
- });
- if (getBiometricContext().isAwake()) {
- mALSProbeCallback.getProbe().enable();
- }
-
- return cancel;
- }
-
- private void startAuthentication() {
+ private void doAuthenticate() throws RemoteException {
final AidlSession session = getFreshDaemon();
final OperationContextExt opContext = getOperationContext();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 8d2b46f..1db2fad 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.hardware.biometrics.BiometricRequestConstants;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -31,7 +30,6 @@
import android.util.Slog;
import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -106,13 +104,8 @@
resetIgnoreDisplayTouches();
mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
this);
-
try {
- if (Flags.deHidl()) {
- startDetectInteraction();
- } else {
- mCancellationSignal = doDetectInteraction();
- }
+ doDetectInteraction();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting finger detect", e);
mSensorOverlays.hide(getSensorId());
@@ -120,33 +113,7 @@
}
}
- private ICancellationSignal doDetectInteraction() throws RemoteException {
- final AidlSession session = getFreshDaemon();
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().detectInteractionWithContext(
- opContext.toAidlContext(mOptions));
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().detectInteraction();
- }
- }
-
- private void startDetectInteraction() throws RemoteException {
+ private void doDetectInteraction() throws RemoteException {
final AidlSession session = getFreshDaemon();
if (session.hasContextMethods()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index a24ab1d..86ebabe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -26,7 +26,6 @@
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
-import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintEnrollOptions;
@@ -40,7 +39,6 @@
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -209,11 +207,7 @@
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
- if (Flags.deHidl()) {
- startEnroll();
- } else {
- mCancellationSignal = doEnroll();
- }
+ doEnroll();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting enroll", e);
onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS,
@@ -222,35 +216,7 @@
}
}
- private ICancellationSignal doEnroll() throws RemoteException {
- final AidlSession session = getFreshDaemon();
- final HardwareAuthToken hat =
- HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
-
- if (session.hasContextMethods()) {
- final OperationContextExt opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().enrollWithContext(
- hat, opContext.toAidlContext());
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- // TODO(b/317414324): Deprecate setIgnoreDisplayTouches
- if (ctx.operationState != null && ctx.operationState.getTag()
- == OperationState.fingerprintOperationState) {
- session.getSession().setIgnoreDisplayTouches(
- ctx.operationState.getFingerprintOperationState().isHardwareIgnoringTouches);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
- return cancel;
- } else {
- return session.getSession().enroll(hat);
- }
- }
-
- private void startEnroll() throws RemoteException {
+ private void doEnroll() throws RemoteException {
final AidlSession session = getFreshDaemon();
final HardwareAuthToken hat =
HardwareAuthTokenUtils.toHardwareAuthToken(mHardwareAuthToken);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 9290f8a..beb3f2f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -29,12 +29,10 @@
import android.content.res.TypedArray;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IInvalidationCallback;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.SensorLocationInternal;
-import android.hardware.biometrics.common.ComponentInfo;
import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
@@ -50,10 +48,8 @@
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
@@ -63,7 +59,6 @@
import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -96,10 +91,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
/**
* Provider for a single instance of the {@link IFingerprint} HAL.
@@ -200,11 +193,7 @@
mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFingerprintSensors = new SensorList<>(ActivityManager.getService());
- if (Flags.deHidl()) {
- mHandler = biometricHandlerProvider.getFingerprintHandler();
- } else {
- mHandler = new Handler(Looper.getMainLooper());
- }
+ mHandler = biometricHandlerProvider.getFingerprintHandler();
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
mTaskStackListener = new BiometricTaskStackListener();
@@ -230,66 +219,19 @@
private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props,
GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- if (Flags.deHidl()) {
- if (!resetLockoutRequiresHardwareAuthToken) {
- Slog.d(getTag(), "Adding HIDL configs");
- for (SensorProps sensorConfig: props) {
- addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
- resetLockoutRequiresHardwareAuthToken);
- }
- } else {
- Slog.d(getTag(), "Adding AIDL configs");
- final List<SensorLocationInternal> workaroundLocations =
- getWorkaroundSensorProps(mContext);
- for (SensorProps prop : props) {
- addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
- resetLockoutRequiresHardwareAuthToken);
- }
+ if (!resetLockoutRequiresHardwareAuthToken) {
+ Slog.d(getTag(), "Adding HIDL configs");
+ for (SensorProps sensorConfig: props) {
+ addHidlSensors(sensorConfig, gestureAvailabilityDispatcher,
+ resetLockoutRequiresHardwareAuthToken);
}
} else {
+ Slog.d(getTag(), "Adding AIDL configs");
final List<SensorLocationInternal> workaroundLocations =
getWorkaroundSensorProps(mContext);
-
for (SensorProps prop : props) {
- final int sensorId = prop.commonProps.sensorId;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- if (prop.commonProps.componentInfo != null) {
- for (ComponentInfo info : prop.commonProps.componentInfo) {
- componentInfo.add(new ComponentInfoInternal(info.componentId,
- info.hardwareVersion, info.firmwareVersion, info.serialNumber,
- info.softwareVersion));
- }
- }
- final FingerprintSensorPropertiesInternal internalProp =
- new FingerprintSensorPropertiesInternal(prop.commonProps.sensorId,
- prop.commonProps.sensorStrength,
- prop.commonProps.maxEnrollmentsPerUser,
- componentInfo,
- prop.sensorType,
- prop.halControlsIllumination,
- true /* resetLockoutRequiresHardwareAuthToken */,
- !workaroundLocations.isEmpty() ? workaroundLocations :
- Arrays.stream(prop.sensorLocations).map(
- location -> new SensorLocationInternal(
- location.display,
- location.sensorLocationX,
- location.sensorLocationY,
- location.sensorRadius))
- .collect(Collectors.toList()));
- final Sensor sensor = new Sensor(this, mContext, mHandler, internalProp,
- mBiometricContext);
- sensor.init(gestureAvailabilityDispatcher, mLockoutResetDispatcher);
- final int sessionUserId =
- sensor.getLazySession().get() == null ? UserHandle.USER_NULL :
- sensor.getLazySession().get().getUserId();
- mFingerprintSensors.addSensor(sensorId, sensor, sessionUserId,
- new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(sensorId, newUserId, null /* callback */);
- }
- });
- Slog.d(getTag(), "Added: " + mFingerprintSensors.get(sensorId).toString());
+ addAidlSensors(prop, gestureAvailabilityDispatcher, workaroundLocations,
+ resetLockoutRequiresHardwareAuthToken);
}
}
}
@@ -546,23 +488,7 @@
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason, options);
- if (Flags.deHidl()) {
- scheduleForSensor(sensorId, client, mBiometricStateCallback);
- } else {
- scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
- mBiometricStateCallback, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- ClientMonitorCallback.super.onClientFinished(
- clientMonitor, success);
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- }));
- }
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
}
@@ -605,13 +531,8 @@
final int userId = options.getUserId();
final int sensorId = options.getSensorId();
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final LockoutTracker lockoutTracker;
- if (Flags.deHidl()) {
- lockoutTracker = mFingerprintSensors.get(sensorId)
- .getLockoutTracker(true /* forAuth */);
- } else {
- lockoutTracker = null;
- }
+ final LockoutTracker lockoutTracker = mFingerprintSensors.get(sensorId)
+ .getLockoutTracker(true /* forAuth */);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
@@ -622,22 +543,16 @@
mTaskStackListener,
mUdfpsOverlayController, mSidefpsController,
mAuthenticationStateListeners, allowBackgroundAuthentication,
- mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
+ mFingerprintSensors.get(sensorId).getSensorProperties(),
Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock(),
lockoutTracker);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
mBiometricStateCallback.onClientStarted(clientMonitor);
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authStartedFor(userId, sensorId,
- requestId));
- } else {
- mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authStartedFor(userId, sensorId, requestId));
}
@Override
@@ -649,15 +564,10 @@
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (Flags.deHidl()) {
- mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId,
- success));
- } else {
- mAuthSessionCoordinator.authEndedFor(userId,
- Utils.getCurrentStrength(sensorId), sensorId, requestId, success);
- }
+ mBiometricHandlerProvider.getBiometricCallbackHandler().post(() ->
+ mAuthSessionCoordinator.authEndedFor(userId,
+ Utils.getCurrentStrength(sensorId), sensorId, requestId,
+ success));
}
});
@@ -764,10 +674,7 @@
@Override
public boolean isHardwareDetected(int sensorId) {
- if (Flags.deHidl()) {
- return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
- }
- return hasHalInstance();
+ return mFingerprintSensors.get(sensorId).isHardwareDetected(mHalInstanceName);
}
@Override
@@ -805,12 +712,7 @@
@Override
public int getLockoutModeForUser(int sensorId, int userId) {
- if (Flags.deHidl()) {
- return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
- } else {
- return mBiometricContext.getAuthSessionCoordinator().getLockoutStateFor(userId,
- Utils.getCurrentStrength(sensorId));
- }
+ return mFingerprintSensors.get(sensorId).getLockoutModeForUser(userId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index af88c62..b7e3f70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -42,7 +42,6 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -58,7 +57,6 @@
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.StartUserClient;
import com.android.server.biometrics.sensors.StopUserClient;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -110,13 +108,6 @@
}
Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
- @NonNull Handler handler, @NonNull FingerprintSensorPropertiesInternal sensorProperties,
- @NonNull BiometricContext biometricContext) {
- this(provider, context, handler, sensorProperties,
- biometricContext, null);
- }
-
- Sensor(@NonNull FingerprintProvider provider, @NonNull Context context,
@NonNull Handler handler, @NonNull SensorProps sensorProp,
@NonNull BiometricContext biometricContext,
@NonNull List<SensorLocationInternal> workaroundLocation,
@@ -131,13 +122,8 @@
*/
public void init(@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
- if (Flags.deHidl()) {
- setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
- lockoutResetDispatcher));
- } else {
- setScheduler(getUserAwareBiometricSchedulerForInit(gestureAvailabilityDispatcher,
- lockoutResetDispatcher));
- }
+ setScheduler(getBiometricSchedulerForInit(gestureAvailabilityDispatcher,
+ lockoutResetDispatcher));
mLockoutTracker = new LockoutCache();
mLazySession = () -> mCurrentSession != null ? mCurrentSession : null;
}
@@ -168,7 +154,7 @@
final AidlResponseHandler resultController = new AidlResponseHandler(
mContext, mScheduler, sensorId, newUserId,
mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {},
+ mBiometricContext.getAuthSessionCoordinator(),
new AidlResponseHandler.AidlResponseHandlerCallback() {
@Override
public void onEnrollSuccess() {
@@ -192,45 +178,6 @@
});
}
- private UserAwareBiometricScheduler<ISession, AidlSession>
- getUserAwareBiometricSchedulerForInit(
- GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- LockoutResetDispatcher lockoutResetDispatcher) {
- return new UserAwareBiometricScheduler<>(TAG,
- BiometricScheduler.sensorTypeFromFingerprintProperties(mSensorProperties),
- gestureAvailabilityDispatcher,
- () -> mCurrentSession != null ? mCurrentSession.getUserId() : UserHandle.USER_NULL,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new FingerprintStopUserClient(mContext,
- () -> mLazySession.get().getSession(), mToken,
- userId, mSensorProperties.sensorId,
- BiometricLogger.ofUnknown(mContext), mBiometricContext,
- () -> mCurrentSession = null);
- }
-
- @NonNull
- @Override
- public StartUserClient<IFingerprint, ISession> getStartUserClient(
- int newUserId) {
- final int sensorId = mSensorProperties.sensorId;
-
- final AidlResponseHandler resultController = new AidlResponseHandler(
- mContext, mScheduler, sensorId, newUserId,
- mLockoutTracker, lockoutResetDispatcher,
- mBiometricContext.getAuthSessionCoordinator(), () -> {
- Slog.e(TAG, "Fingerprint hardware unavailable.");
- mCurrentSession = null;
- });
-
- return Sensor.this.getStartUserClient(resultController, sensorId,
- newUserId);
- }
- });
- }
-
private FingerprintStartUserClient getStartUserClient(AidlResponseHandler resultController,
int sensorId, int newUserId) {
final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
deleted file mode 100644
index fc037ae..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * A test session implementation for the {@link Fingerprint21} provider. See
- * {@link android.hardware.biometrics.BiometricTestSession}.
- */
-public class BiometricTestSessionImpl extends ITestSession.Stub {
-
- private static final String TAG = "BiometricTestSessionImpl";
-
- @NonNull private final Context mContext;
- private final int mSensorId;
- @NonNull private final ITestSessionCallback mCallback;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final Fingerprint21 mFingerprint21;
- @NonNull private final Fingerprint21.HalResultController mHalResultController;
- @NonNull private final Set<Integer> mEnrollmentIds;
- @NonNull private final Random mRandom;
-
- /**
- * Internal receiver currently only used for enroll. Results do not need to be forwarded to the
- * test, since enrollment is a platform-only API. The authentication path is tested through
- * the public FingerprintManager APIs and does not use this receiver.
- */
- private final IFingerprintServiceReceiver mReceiver = new IFingerprintServiceReceiver.Stub() {
- @Override
- public void onEnrollResult(Fingerprint fp, int remaining) {
-
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
-
- }
-
- @Override
- public void onAuthenticationSucceeded(Fingerprint fp, int userId,
- boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric) {
-
- }
-
- @Override
- public void onAuthenticationFailed() {
-
- }
-
- @Override
- public void onError(int error, int vendorCode) {
-
- }
-
- @Override
- public void onRemoved(Fingerprint fp, int remaining) {
-
- }
-
- @Override
- public void onChallengeGenerated(int sensorId, int userId, long challenge) {
-
- }
-
- @Override
- public void onUdfpsPointerDown(int sensorId) {
-
- }
-
- @Override
- public void onUdfpsPointerUp(int sensorId) {
-
- }
-
- @Override
- public void onUdfpsOverlayShown() {
-
- }
- };
-
- BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull Fingerprint21 fingerprint21,
- @NonNull Fingerprint21.HalResultController halResultController) {
- mContext = context;
- mSensorId = sensorId;
- mCallback = callback;
- mFingerprint21 = fingerprint21;
- mBiometricStateCallback = biometricStateCallback;
- mHalResultController = halResultController;
- mEnrollmentIds = new HashSet<>();
- mRandom = new Random();
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void setTestHalEnabled(boolean enabled) {
-
- super.setTestHalEnabled_enforcePermission();
-
- mFingerprint21.setTestHalEnabled(enabled);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void startEnroll(int userId) {
-
- super.startEnroll_enforcePermission();
-
- mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- (new FingerprintEnrollOptions.Builder()).build());
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void finishEnroll(int userId) {
-
- super.finishEnroll_enforcePermission();
-
- int nextRandomId = mRandom.nextInt();
- while (mEnrollmentIds.contains(nextRandomId)) {
- nextRandomId = mRandom.nextInt();
- }
-
- mEnrollmentIds.add(nextRandomId);
- mHalResultController.onEnrollResult(0 /* deviceId */,
- nextRandomId /* fingerId */, userId, 0);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void acceptAuthentication(int userId) {
-
- // Fake authentication with any of the existing fingers
- super.acceptAuthentication_enforcePermission();
-
- List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId);
- if (fingerprints.isEmpty()) {
- Slog.w(TAG, "No fingerprints, returning");
- return;
- }
- final int fid = fingerprints.get(0).getBiometricId();
- final ArrayList<Byte> hat = new ArrayList<>(Collections.nCopies(69, (byte) 0));
- mHalResultController.onAuthenticated(0 /* deviceId */, fid, userId, hat);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void rejectAuthentication(int userId) {
-
- super.rejectAuthentication_enforcePermission();
-
- mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyAcquired(int userId, int acquireInfo) {
-
- super.notifyAcquired_enforcePermission();
-
- mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void notifyError(int userId, int errorCode) {
-
- super.notifyError_enforcePermission();
-
- mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */);
- }
-
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- @Override
- public void cleanupInternalState(int userId) {
-
- super.cleanupInternalState_enforcePermission();
-
- mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- try {
- mCallback.onCleanupStarted(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- try {
- mCallback.onCleanupFinished(clientMonitor.getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
- });
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
deleted file mode 100644
index 33e448b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ /dev/null
@@ -1,1289 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.SynchronousUserSwitchObserver;
-import android.app.TaskStackListener;
-import android.app.UserSwitchObserver;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricsProtoEnums;
-import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.ITestSession;
-import android.hardware.biometrics.ITestSessionCallback;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IHwBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Slog;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
-import com.android.server.biometrics.AuthenticationStatsCollector;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.SensorServiceStateProto;
-import com.android.server.biometrics.SensorStateProto;
-import com.android.server.biometrics.UserStateProto;
-import com.android.server.biometrics.Utils;
-import com.android.server.biometrics.fingerprint.FingerprintServiceDumpProto;
-import com.android.server.biometrics.fingerprint.FingerprintUserStatsProto;
-import com.android.server.biometrics.fingerprint.PerformanceStatsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthSessionCoordinator;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnumerateConsumer;
-import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.LockoutCache;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.RemovalConsumer;
-import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlResponseHandler;
-import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Supplier;
-
-/**
- * Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
- * its extended minor versions.
- */
-public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider {
-
- private static final String TAG = "Fingerprint21";
- private static final int ENROLL_TIMEOUT_SEC = 60;
-
- private boolean mTestHalEnabled;
-
- final Context mContext;
- @NonNull private final BiometricStateCallback mBiometricStateCallback;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
- private final ActivityTaskManager mActivityTaskManager;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- private final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
- private final Handler mHandler;
- private final LockoutResetDispatcher mLockoutResetDispatcher;
- private final LockoutFrameworkImpl mLockoutTracker;
- private final BiometricTaskStackListener mTaskStackListener;
- private final Supplier<IBiometricsFingerprint> mLazyDaemon;
- private final Map<Integer, Long> mAuthenticatorIds;
-
- @Nullable private IBiometricsFingerprint mDaemon;
- @NonNull private final HalResultController mHalResultController;
- @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
-
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable private ISidefpsController mSidefpsController;
- @NonNull private final BiometricContext mBiometricContext;
- @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
- // for requests that do not use biometric prompt
- @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
- private int mCurrentUserId = UserHandle.USER_NULL;
- private final boolean mIsUdfps;
- private final int mSensorId;
- private final boolean mIsPowerbuttonFps;
- private AidlSession mSession;
-
- private final class BiometricTaskStackListener extends TaskStackListener {
- @Override
- public void onTaskStackChanged() {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationClient)) {
- Slog.e(TAG, "Task stack changed for client: " + client);
- return;
- }
- if (Utils.isKeyguard(mContext, client.getOwnerString())
- || Utils.isSystem(mContext, client.getOwnerString())) {
- return; // Keyguard is always allowed
- }
-
- if (Utils.isBackground(client.getOwnerString())
- && !client.isAlreadyDone()) {
- Slog.e(TAG, "Stopping background authentication,"
- + " currentClient: " + client);
- mScheduler.cancelAuthenticationOrDetection(
- client.getToken(), client.getRequestId());
- }
- });
- }
- }
-
- private final LockoutFrameworkImpl.LockoutResetCallback mLockoutResetCallback =
- new LockoutFrameworkImpl.LockoutResetCallback() {
- @Override
- public void onLockoutReset(int userId) {
- mLockoutResetDispatcher.notifyLockoutResetCallbacks(mSensorProperties.sensorId);
- }
- };
-
- private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() {
- @Override
- public void onUserSwitching(int newUserId) {
- scheduleInternalCleanup(newUserId, null /* callback */);
- }
- };
-
- public static class HalResultController extends IBiometricsFingerprintClientCallback.Stub {
-
- /**
- * Interface to sends results to the HalResultController's owner.
- */
- public interface Callback {
- /**
- * Invoked when the HAL sends ERROR_HW_UNAVAILABLE.
- */
- void onHardwareUnavailable();
- }
-
- private final int mSensorId;
- @NonNull private final Context mContext;
- @NonNull final Handler mHandler;
- @NonNull final BiometricScheduler<IBiometricsFingerprint, AidlSession> mScheduler;
- @Nullable private Callback mCallback;
-
- HalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler) {
- mSensorId = sensorId;
- mContext = context;
- mHandler = handler;
- mScheduler = scheduler;
- }
-
- public void setCallback(@Nullable Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof FingerprintEnrollClient)) {
- Slog.e(TAG, "onEnrollResult for non-enroll client: "
- + Utils.getClientName(client));
- return;
- }
-
- final int currentUserId = client.getTargetUserId();
- final CharSequence name = FingerprintUtils.getLegacyInstance(mSensorId)
- .getUniqueName(mContext, currentUserId);
- final Fingerprint fingerprint = new Fingerprint(name, groupId, fingerId, deviceId);
-
- final FingerprintEnrollClient enrollClient = (FingerprintEnrollClient) client;
- enrollClient.onEnrollResult(fingerprint, remaining);
- });
- }
-
- @Override
- public void onAcquired(long deviceId, int acquiredInfo, int vendorCode) {
- onAcquired_2_2(deviceId, acquiredInfo, vendorCode);
- }
-
- @Override
- public void onAcquired_2_2(long deviceId, int acquiredInfo, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AcquisitionClient)) {
- Slog.e(TAG, "onAcquired for non-acquisition client: "
- + Utils.getClientName(client));
- return;
- }
-
- final AcquisitionClient<?> acquisitionClient = (AcquisitionClient<?>) client;
- acquisitionClient.onAcquired(acquiredInfo, vendorCode);
- });
- }
-
- @Override
- public void onAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "onAuthenticated for non-authentication consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- final boolean authenticated = fingerId != 0;
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- authenticationConsumer.onAuthenticated(fp, authenticated, token);
- });
- }
-
- @Override
- public void onError(long deviceId, int error, int vendorCode) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- Slog.d(TAG, "handleError"
- + ", client: " + Utils.getClientName(client)
- + ", error: " + error
- + ", vendorCode: " + vendorCode);
- if (!(client instanceof ErrorConsumer)) {
- Slog.e(TAG, "onError for non-error consumer: " + Utils.getClientName(client));
- return;
- }
-
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(error, vendorCode);
-
- if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
- Slog.e(TAG, "Got ERROR_HW_UNAVAILABLE");
- if (mCallback != null) {
- mCallback.onHardwareUnavailable();
- }
- }
- });
- }
-
- @Override
- public void onRemoved(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- Slog.d(TAG, "Removed, fingerId: " + fingerId + ", remaining: " + remaining);
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof RemovalConsumer)) {
- Slog.e(TAG, "onRemoved for non-removal consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- final RemovalConsumer removalConsumer = (RemovalConsumer) client;
- removalConsumer.onRemoved(fp, remaining);
- });
- }
-
- @Override
- public void onEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof EnumerateConsumer)) {
- Slog.e(TAG, "onEnumerate for non-enumerate consumer: "
- + Utils.getClientName(client));
- return;
- }
-
- final Fingerprint fp = new Fingerprint("", groupId, fingerId, deviceId);
- final EnumerateConsumer enumerateConsumer = (EnumerateConsumer) client;
- enumerateConsumer.onEnumerationResult(fp, remaining);
- });
- }
- }
-
- @VisibleForTesting
- Fingerprint21(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller,
- @NonNull BiometricContext biometricContext) {
- mContext = context;
- mBiometricStateCallback = biometricStateCallback;
- mAuthenticationStateListeners = authenticationStateListeners;
- mBiometricContext = biometricContext;
-
- mSensorProperties = sensorProps;
- mSensorId = sensorProps.sensorId;
- mIsUdfps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
- || sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC;
- mIsPowerbuttonFps = sensorProps.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON;
-
- mScheduler = scheduler;
- mHandler = handler;
- mActivityTaskManager = ActivityTaskManager.getInstance();
- mTaskStackListener = new BiometricTaskStackListener();
- mAuthenticatorIds = Collections.synchronizedMap(new HashMap<>());
- mLazyDaemon = Fingerprint21.this::getDaemon;
- mLockoutResetDispatcher = lockoutResetDispatcher;
- mLockoutTracker = new LockoutFrameworkImpl(context, mLockoutResetCallback);
- mHalResultController = controller;
- mHalResultController.setCallback(() -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- });
-
- AuthenticationStatsBroadcastReceiver mBroadcastReceiver =
- new AuthenticationStatsBroadcastReceiver(
- mContext,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- (AuthenticationStatsCollector collector) -> {
- Slog.d(TAG, "Initializing AuthenticationStatsCollector");
- mAuthenticationStatsCollector = collector;
- });
-
- try {
- ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to register user switch observer");
- }
- }
-
- public static Fingerprint21 newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- final BiometricScheduler<IBiometricsFingerprint, AidlSession> scheduler =
- new BiometricScheduler<>(
- BiometricScheduler.sensorTypeFromFingerprintProperties(sensorProps),
- gestureAvailabilityDispatcher);
- final HalResultController controller = new HalResultController(sensorProps.sensorId,
- context, handler, scheduler);
- return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners,
- sensorProps, scheduler, handler, lockoutResetDispatcher, controller,
- BiometricContext.getInstance(context));
- }
-
- @Override
- public void serviceDied(long cookie) {
- Slog.e(TAG, "HAL died");
- mHandler.post(() -> {
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId)
- .incrementHALDeathCount();
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (client instanceof ErrorConsumer) {
- Slog.e(TAG, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
- final ErrorConsumer errorConsumer = (ErrorConsumer) client;
- errorConsumer.onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
-
- FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
- BiometricsProtoEnums.MODALITY_FINGERPRINT,
- BiometricsProtoEnums.ISSUE_HAL_DEATH,
- -1 /* sensorId */);
- }
-
- mScheduler.recordCrashState();
- mScheduler.reset();
- });
- }
-
- synchronized AidlSession getSession() {
- if (mDaemon != null && mSession != null) {
- return mSession;
- } else {
- return mSession = new AidlSession(this::getDaemon,
- mCurrentUserId, new AidlResponseHandler(mContext,
- mScheduler, mSensorId, mCurrentUserId, new LockoutCache(),
- mLockoutResetDispatcher, new AuthSessionCoordinator(), () -> {
- mDaemon = null;
- mCurrentUserId = UserHandle.USER_NULL;
- }));
- }
- }
-
- @VisibleForTesting
- synchronized IBiometricsFingerprint getDaemon() {
- if (mTestHalEnabled) {
- final TestHal testHal = new TestHal(mContext, mSensorId);
- testHal.setNotify(mHalResultController);
- return testHal;
- }
-
- if (mDaemon != null) {
- return mDaemon;
- }
-
- Slog.d(TAG, "Daemon was null, reconnecting, current operation: "
- + mScheduler.getCurrentClient());
- try {
- mDaemon = IBiometricsFingerprint.getService();
- } catch (java.util.NoSuchElementException e) {
- // Service doesn't exist or cannot be opened.
- Slog.w(TAG, "NoSuchElementException", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to get fingerprint HAL", e);
- }
-
- if (mDaemon == null) {
- Slog.w(TAG, "Fingerprint HAL not available");
- return null;
- }
-
- mDaemon.asBinder().linkToDeath(this, 0 /* flags */);
-
- // HAL ID for these HIDL versions are only used to determine if callbacks have been
- // successfully set.
- long halId = 0;
- try {
- halId = mDaemon.setNotify(mHalResultController);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set callback for fingerprint HAL", e);
- mDaemon = null;
- }
-
- Slog.d(TAG, "Fingerprint HAL ready, HAL ID: " + halId);
- if (halId != 0) {
- scheduleLoadAuthenticatorIds();
- scheduleInternalCleanup(ActivityManager.getCurrentUser(), null /* callback */);
- } else {
- Slog.e(TAG, "Unable to set callback");
- mDaemon = null;
- }
-
- return mDaemon;
- }
-
- @Nullable IUdfpsOverlayController getUdfpsOverlayController() {
- return mUdfpsOverlayController;
- }
-
- private void scheduleLoadAuthenticatorIds() {
- // Note that this can be performed on the scheduler (as opposed to being done immediately
- // when the HAL is (re)loaded, since
- // 1) If this is truly the first time it's being performed (e.g. system has just started),
- // this will be run very early and way before any applications need to generate keys.
- // 2) If this is being performed to refresh the authenticatorIds (e.g. HAL crashed and has
- // just been reloaded), the framework already has a cache of the authenticatorIds. This
- // is safe because authenticatorIds only change when A) new template has been enrolled,
- // or B) all templates are removed.
- mHandler.post(() -> {
- for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
- final int targetUserId = user.id;
- if (!mAuthenticatorIds.containsKey(targetUserId)) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId, true /* force */);
- }
- }
- });
- }
-
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId) {
- scheduleUpdateActiveUserWithoutHandler(targetUserId, false /* force */);
- }
-
- /**
- * Schedules the {@link FingerprintUpdateActiveUserClient} without posting the work onto the
- * handler. Many/most APIs are user-specific. However, the HAL requires explicit "setActiveUser"
- * invocation prior to authenticate/enroll/etc. Thus, internally we usually want to schedule
- * this operation on the same lambda/runnable as those operations so that the ordering is
- * correct.
- *
- * @param targetUserId Switch to this user, and update their authenticatorId
- * @param force Always retrieve the authenticatorId, even if we are already the targetUserId
- */
- private void scheduleUpdateActiveUserWithoutHandler(int targetUserId, boolean force) {
- final boolean hasEnrolled =
- !getEnrolledFingerprints(mSensorProperties.sensorId, targetUserId).isEmpty();
- final FingerprintUpdateActiveUserClientLegacy client =
- new FingerprintUpdateActiveUserClientLegacy(mContext, mLazyDaemon, targetUserId,
- mContext.getOpPackageName(), mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext,
- this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- if (mCurrentUserId != targetUserId) {
- // Create new session with updated user ID
- mSession = null;
- }
- mCurrentUserId = targetUserId;
- } else {
- Slog.w(TAG, "Failed to change user, still: " + mCurrentUserId);
- }
- }
- });
- }
-
- private int getCurrentUser() {
- return mCurrentUserId;
- }
-
- @Override
- public boolean containsSensor(int sensorId) {
- return mSensorProperties.sensorId == sensorId;
- }
-
- @Override
- @NonNull
- public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @Nullable
- @Override
- public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId) {
- return mSensorProperties;
- }
-
- @Override
- public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
- // Fingerprint2.1 keeps track of lockout in the framework. Let's just do it on the handler
- // thread.
- mHandler.post(() -> {
- if (Flags.deHidl()) {
- scheduleResetLockoutAidl(sensorId, userId, hardwareAuthToken);
- } else {
- scheduleResetLockoutHidl(sensorId, userId);
- }
- });
- }
-
- private void scheduleResetLockoutAidl(int sensorId, int userId,
- @Nullable byte[] hardwareAuthToken) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient(
- mContext, this::getSession, userId, mContext.getOpPackageName(),
- sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, hardwareAuthToken, mLockoutTracker,
- mLockoutResetDispatcher,
- Utils.getCurrentStrength(sensorId));
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleResetLockoutHidl(int sensorId, int userId) {
- final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
- userId, mContext.getOpPackageName(), sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, mLockoutTracker);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
- mHandler.post(() -> {
- if (Flags.deHidl()) {
- scheduleGenerateChallengeAidl(userId, token, receiver, opPackageName);
- } else {
- scheduleGenerateChallengeHidl(userId, token, receiver, opPackageName);
- }
- });
- }
-
- private void scheduleGenerateChallengeAidl(int userId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintGenerateChallengeClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleGenerateChallengeHidl(int userId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName) {
- final FingerprintGenerateChallengeClient client =
- new FingerprintGenerateChallengeClient(mContext, mLazyDaemon, token,
- new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
- @NonNull String opPackageName, long challenge) {
- mHandler.post(() -> {
- if (Flags.deHidl()) {
- scheduleRevokeChallengeAidl(userId, token, opPackageName);
- } else {
- scheduleRevokeChallengeHidl(userId, token, opPackageName);
- }
- });
- }
-
- private void scheduleRevokeChallengeAidl(int userId, @NonNull IBinder token,
- @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRevokeChallengeClient(
- mContext, this::getSession,
- token, userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, 0L);
- mScheduler.scheduleClientMonitor(client);
- }
-
- private void scheduleRevokeChallengeHidl(int userId, @NonNull IBinder token,
- @NonNull String opPackageName) {
- final FingerprintRevokeChallengeClient client = new FingerprintRevokeChallengeClient(
- mContext, mLazyDaemon, token, userId, opPackageName,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext);
- mScheduler.scheduleClientMonitor(client);
- }
-
- @Override
- public long scheduleEnroll(int sensorId, @NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintEnrollOptions options) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id, options);
- } else {
- scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id, options);
- }
- });
- return id;
- }
-
- private void scheduleEnrollHidl(@NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason, long id,
- @NonNull FingerprintEnrollOptions options) {
- final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
- userId, hardwareAuthToken, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- mSidefpsController,
- mAuthenticationStateListeners, enrollReason, options);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
- true /* force */);
- }
- }
- });
- }
-
- private void scheduleEnrollAidl(@NonNull IBinder token,
- @NonNull byte[] hardwareAuthToken, int userId,
- @NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason, long id,
- @NonNull FingerprintEnrollOptions options) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
- client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
- mContext,
- this::getSession, token, id,
- new ClientMonitorCallbackConverter(receiver),
- userId, hardwareAuthToken, opPackageName,
- FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext, null /* sensorProps */,
- mUdfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- mSidefpsController,
- mAuthenticationStateListeners,
- mContext.getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
- enrollReason, options);
- mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
- if (success) {
- // Update authenticatorIds
- scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
- true /* force */);
- }
- }
- });
- }
-
- @Override
- public void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId) {
- mHandler.post(() -> mScheduler.cancelEnrollment(token, requestId));
- }
-
- @Override
- public long scheduleFingerDetect(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- int statsClient) {
- final long id = mRequestCounter.incrementAndGet();
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
- if (Flags.deHidl()) {
- scheduleFingerDetectAidl(token, listener, options, statsClient, id,
- isStrongBiometric);
- } else {
- scheduleFingerDetectHidl(token, listener, options, statsClient, id,
- isStrongBiometric);
- }
- });
-
- return id;
- }
-
- private void scheduleFingerDetectHidl(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- int statsClient, long id, boolean isStrongBiometric) {
- final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- private void scheduleFingerDetectAidl(@NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- int statsClient, long id, boolean isStrongBiometric) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient
- client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintDetectClient(
- mContext,
- this::getSession, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- @Override
- public void scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(options.getUserId());
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
-
- if (Flags.deHidl()) {
- scheduleAuthenticateAidl(token, operationId, cookie, listener, options, requestId,
- restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
- } else {
- scheduleAuthenticateHidl(token, operationId, cookie, listener, options, requestId,
- restricted, statsClient, allowBackgroundAuthentication, isStrongBiometric);
- }
- });
- }
-
- private void scheduleAuthenticateAidl(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient
- client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintAuthenticationClient(
- mContext, this::getSession, token, requestId, listener, operationId,
- restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric,
- mTaskStackListener,
- mUdfpsOverlayController, mSidefpsController,
- mAuthenticationStateListeners,
- allowBackgroundAuthentication, mSensorProperties, mHandler,
- Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- private void scheduleAuthenticateHidl(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- long requestId, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication, boolean isStrongBiometric) {
- final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, requestId, listener, operationId,
- restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
- mAuthenticationStatsCollector),
- mBiometricContext, isStrongBiometric,
- mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController,
- mAuthenticationStateListeners,
- allowBackgroundAuthentication, mSensorProperties,
- Utils.getCurrentStrength(mSensorId));
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- @Override
- public long scheduleAuthenticate(@NonNull IBinder token, long operationId,
- int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication) {
- final long id = mRequestCounter.incrementAndGet();
-
- scheduleAuthenticate(token, operationId, cookie, listener,
- options, id, restricted, statsClient, allowBackgroundAuthentication);
-
- return id;
- }
-
- @Override
- public void startPreparedClient(int sensorId, int cookie) {
- mHandler.post(() -> mScheduler.startPreparedClient(cookie));
- }
-
- @Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
- Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
- }
-
- @Override
- public void scheduleRemove(int sensorId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
- @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, receiver, fingerId, userId, opPackageName);
- } else {
- scheduleRemoveHidl(token, receiver, fingerId, userId, opPackageName);
- }
- });
- }
-
- private void scheduleRemoveHidl(@NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
- @NonNull String opPackageName) {
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
- userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- private void scheduleRemoveAidl(@NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
- @NonNull String opPackageName) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintRemovalClient(
- mContext, this::getSession, token,
- new ClientMonitorCallbackConverter(receiver), new int[]{fingerId}, userId,
- opPackageName, FingerprintUtils.getLegacyInstance(mSensorId), mSensorId,
- createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
- }
-
- @Override
- public void scheduleRemoveAll(int sensorId, @NonNull IBinder token,
- @NonNull IFingerprintServiceReceiver receiver, int userId,
- @NonNull String opPackageName) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- // For IBiometricsFingerprint@2.1, remove(0) means remove all enrollments
- if (Flags.deHidl()) {
- scheduleRemoveAidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
- } else {
- scheduleRemoveHidl(token, receiver, 0 /* fingerId */, userId, opPackageName);
- }
- });
- }
-
- private void scheduleInternalCleanup(int userId,
- @Nullable ClientMonitorCallback callback) {
- mHandler.post(() -> {
- scheduleUpdateActiveUserWithoutHandler(userId);
-
- if (Flags.deHidl()) {
- scheduleInternalCleanupAidl(userId, callback);
- } else {
- scheduleInternalCleanupHidl(userId, callback);
- }
- });
- }
-
- private void scheduleInternalCleanupHidl(int userId,
- @Nullable ClientMonitorCallback callback) {
- final FingerprintInternalCleanupClient client = new FingerprintInternalCleanupClient(
- mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext,
- FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
- }
-
- private void scheduleInternalCleanupAidl(int userId,
- @Nullable ClientMonitorCallback callback) {
- final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient
- client =
- new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintInternalCleanupClient(
- mContext, this::getSession, userId, mContext.getOpPackageName(),
- mSensorProperties.sensorId,
- createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN,
- mAuthenticationStatsCollector),
- mBiometricContext,
- FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback) {
- scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
- }
-
- @Override
- public void scheduleInternalCleanup(int sensorId, int userId,
- @Nullable ClientMonitorCallback callback, boolean favorHalEnrollments) {
- scheduleInternalCleanup(userId, new ClientMonitorCompositeCallback(callback,
- mBiometricStateCallback));
- }
-
- private BiometricLogger createLogger(int statsAction, int statsClient,
- AuthenticationStatsCollector authenticationStatsCollector) {
- return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient, authenticationStatsCollector);
- }
-
- @Override
- public boolean isHardwareDetected(int sensorId) {
- return getDaemon() != null;
- }
-
- @Override
- public void rename(int sensorId, int fingerId, int userId, @NonNull String name) {
- mHandler.post(() -> {
- FingerprintUtils.getLegacyInstance(mSensorId)
- .renameBiometricForUser(mContext, userId, fingerId, name);
- });
- }
-
- @Override
- @NonNull
- public List<Fingerprint> getEnrolledFingerprints(int sensorId, int userId) {
- return FingerprintUtils.getLegacyInstance(mSensorId).getBiometricsForUser(mContext, userId);
- }
-
- @Override
- public boolean hasEnrollments(int sensorId, int userId) {
- return !getEnrolledFingerprints(sensorId, userId).isEmpty();
- }
-
- @Override
- @LockoutTracker.LockoutMode public int getLockoutModeForUser(int sensorId, int userId) {
- return mLockoutTracker.getLockoutModeForUser(userId);
- }
-
- @Override
- public long getAuthenticatorId(int sensorId, int userId) {
- return mAuthenticatorIds.getOrDefault(userId, 0L);
- }
-
- @Override
- public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onFingerDown received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerDown(pc);
- });
- }
-
- @Override
- public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onFingerDown received during client: " + client);
- return;
- }
- ((Udfps) client).onPointerUp(pc);
- });
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event, long requestId,
- int sensorId) {
- mScheduler.getCurrentClientIfMatches(requestId, (client) -> {
- if (!(client instanceof Udfps)) {
- Slog.w(TAG, "onUdfpsUiEvent received during client: " + client);
- return;
- }
- ((Udfps) client).onUdfpsUiEvent(event);
- });
- }
-
- @Override
- public void onPowerPressed() {
- Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
- }
-
- @Override
- public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
- mUdfpsOverlayController = controller;
- }
-
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Override
- public void setSidefpsController(@NonNull ISidefpsController controller) {
- mSidefpsController = controller;
- }
-
- @Override
- public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
- boolean clearSchedulerBuffer) {
- final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
-
- proto.write(SensorStateProto.SENSOR_ID, mSensorProperties.sensorId);
- proto.write(SensorStateProto.MODALITY, SensorStateProto.FINGERPRINT);
- if (mSensorProperties.isAnyUdfpsType()) {
- proto.write(SensorStateProto.MODALITY_FLAGS, SensorStateProto.FINGERPRINT_UDFPS);
- }
- proto.write(SensorStateProto.CURRENT_STRENGTH,
- Utils.getCurrentStrength(mSensorProperties.sensorId));
- proto.write(SensorStateProto.SCHEDULER, mScheduler.dumpProtoState(clearSchedulerBuffer));
-
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(SensorStateProto.USER_STATES);
- proto.write(UserStateProto.USER_ID, userId);
- proto.write(UserStateProto.NUM_ENROLLED, FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
- proto.end(userToken);
- }
-
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_HARDWARE_AUTH_TOKEN,
- mSensorProperties.resetLockoutRequiresHardwareAuthToken);
- proto.write(SensorStateProto.RESET_LOCKOUT_REQUIRES_CHALLENGE,
- mSensorProperties.resetLockoutRequiresChallenge);
-
- proto.end(sensorToken);
- }
-
- @Override
- public void dumpProtoMetrics(int sensorId, FileDescriptor fd) {
- PerformanceTracker tracker =
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
- final ProtoOutputStream proto = new ProtoOutputStream(fd);
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
-
- final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
-
- proto.write(FingerprintUserStatsProto.USER_ID, userId);
- proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
- FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size());
-
- // Normal fingerprint authentications (e.g. lockscreen)
- long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, tracker.getTimedLockoutForUser(userId));
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT,
- tracker.getPermanentLockoutForUser(userId));
- proto.end(countsToken);
-
- // Statistics about secure fingerprint transactions (e.g. to unlock password
- // storage, make secure purchases, etc.)
- countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
- proto.write(PerformanceStatsProto.ACCEPT, tracker.getAcceptCryptoForUser(userId));
- proto.write(PerformanceStatsProto.REJECT, tracker.getRejectCryptoForUser(userId));
- proto.write(PerformanceStatsProto.ACQUIRE, tracker.getAcquireCryptoForUser(userId));
- proto.write(PerformanceStatsProto.LOCKOUT, 0); // meaningless for crypto
- proto.write(PerformanceStatsProto.PERMANENT_LOCKOUT, 0); // meaningless for crypto
- proto.end(countsToken);
-
- proto.end(userToken);
- }
- proto.flush();
- tracker.clear();
- }
-
- @Override
- public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
- @NonNull IInvalidationCallback callback) {
- // TODO (b/179101888): Remove this temporary workaround.
- try {
- callback.onCompleted();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to complete InvalidateAuthenticatorId");
- }
- }
-
- @Override
- public void dumpInternal(int sensorId, @NonNull PrintWriter pw) {
- PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(mSensorProperties.sensorId);
-
- JSONObject dump = new JSONObject();
- try {
- dump.put("service", TAG);
- dump.put("isUdfps", mIsUdfps);
- dump.put("isPowerbuttonFps", mIsPowerbuttonFps);
-
- JSONArray sets = new JSONArray();
- for (UserInfo user : UserManager.get(mContext).getUsers()) {
- final int userId = user.getUserHandle().getIdentifier();
- final int N = FingerprintUtils.getLegacyInstance(mSensorId)
- .getBiometricsForUser(mContext, userId).size();
- JSONObject set = new JSONObject();
- set.put("id", userId);
- set.put("count", N);
- set.put("accept", performanceTracker.getAcceptForUser(userId));
- set.put("reject", performanceTracker.getRejectForUser(userId));
- set.put("acquire", performanceTracker.getAcquireForUser(userId));
- set.put("lockout", performanceTracker.getTimedLockoutForUser(userId));
- set.put("permanentLockout", performanceTracker.getPermanentLockoutForUser(userId));
- // cryptoStats measures statistics about secure fingerprint transactions
- // (e.g. to unlock password storage, make secure purchases, etc.)
- set.put("acceptCrypto", performanceTracker.getAcceptCryptoForUser(userId));
- set.put("rejectCrypto", performanceTracker.getRejectCryptoForUser(userId));
- set.put("acquireCrypto", performanceTracker.getAcquireCryptoForUser(userId));
- sets.put(set);
- }
-
- dump.put("prints", sets);
- } catch (JSONException e) {
- Slog.e(TAG, "dump formatting failure", e);
- }
- pw.println(dump);
- pw.println("HAL deaths since last reboot: " + performanceTracker.getHALDeathCount());
- mScheduler.dump(pw);
- }
-
- void setTestHalEnabled(boolean enabled) {
- mTestHalEnabled = enabled;
- }
-
- @NonNull
- @Override
- public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- mBiometricStateCallback, this, mHalResultController);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
deleted file mode 100644
index f857946..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ /dev/null
@@ -1,573 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.trust.TrustManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
-import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.IFingerprintServiceReceiver;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Slog;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-/**
- * A mockable/testable provider of the {@link android.hardware.biometrics.fingerprint.V2_3} HIDL
- * interface. This class is intended simulate UDFPS logic for devices that do not have an actual
- * fingerprint@2.3 HAL (where UDFPS starts to become supported)
- *
- * UDFPS "accept" can only happen within a set amount of time after a sensor authentication. This is
- * specified by {@link MockHalResultController#AUTH_VALIDITY_MS}. Touches after this duration will
- * be treated as "reject".
- *
- * This class provides framework logic to emulate, for testing only, the UDFPS functionalies below:
- *
- * 1) IF either A) the caller is keyguard, and the device is not in a trusted state (authenticated
- * via biometric sensor or unlocked with a trust agent {@see android.app.trust.TrustManager}, OR
- * B) the caller is not keyguard, and regardless of trusted state, AND (following applies to both
- * (A) and (B) above) {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- * received, this class will respond with {@link AuthenticationCallback#onAuthenticationFailed()}
- * after a tunable flat_time + variance_time.
- *
- * In the case above (1), callers must not receive a successful authentication event here because
- * the sensor has not actually been authenticated.
- *
- * 2) IF A) the caller is keyguard and the device is not in a trusted state, OR B) the caller is not
- * keyguard and regardless of trusted state, AND (following applies to both (A) and (B)) the
- * sensor is touched and the fingerprint is accepted by the HAL, and then
- * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- * after a tunable flat_time + variance_time. Note that the authentication callback from the
- * sensor is held until {@link FingerprintManager#onFingerDown(int, int, float, float)} is
- * invoked.
- *
- * In the case above (2), callers can receive a successful authentication callback because the
- * real sensor was authenticated. Note that even though the real sensor was touched, keyguard
- * fingerprint authentication does not put keyguard into a trusted state because the
- * authentication callback is held until onFingerDown was invoked. This allows callers such as
- * keyguard to simulate a realistic path.
- *
- * 3) IF the caller is keyguard AND the device in a trusted state and then
- * {@link FingerprintManager#onFingerDown(int, int, float, float)} is received, this class will
- * respond with {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)}
- * after a tunable flat_time + variance time.
- *
- * In the case above (3), since the device is already unlockable via trust agent, it's fine to
- * simulate the successful auth path. Non-keyguard clients will fall into either (1) or (2)
- * above.
- *
- * This class currently does not simulate false rejection. Conversely, this class relies on the
- * real hardware sensor so does not affect false acceptance.
- */
-@SuppressWarnings("deprecation")
-public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManager.TrustListener {
-
- private static final String TAG = "Fingerprint21UdfpsMock";
-
- // Secure setting integer. If true, the system will load this class to enable udfps testing.
- public static final String CONFIG_ENABLE_TEST_UDFPS =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.enable";
- // Secure setting integer. A fixed duration intended to simulate something like the duration
- // required for image capture.
- private static final String CONFIG_AUTH_DELAY_PT1 =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt1";
- // Secure setting integer. A fixed duration intended to simulate something like the duration
- // required for template matching to complete.
- private static final String CONFIG_AUTH_DELAY_PT2 =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_pt2";
- // Secure setting integer. A random value between [-randomness, randomness] will be added to the
- // capture delay above for each accept/reject.
- private static final String CONFIG_AUTH_DELAY_RANDOMNESS =
- "com.android.server.biometrics.sensors.fingerprint.test_udfps.auth_delay_randomness";
-
- private static final int DEFAULT_AUTH_DELAY_PT1_MS = 300;
- private static final int DEFAULT_AUTH_DELAY_PT2_MS = 400;
- private static final int DEFAULT_AUTH_DELAY_RANDOMNESS_MS = 100;
-
- @NonNull private final TestableBiometricScheduler mScheduler;
- @NonNull private final Handler mHandler;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final MockHalResultController mMockHalResultController;
- @NonNull private final TrustManager mTrustManager;
- @NonNull private final SparseBooleanArray mUserHasTrust;
- @NonNull private final Random mRandom;
- @NonNull private final FakeRejectRunnable mFakeRejectRunnable;
- @NonNull private final FakeAcceptRunnable mFakeAcceptRunnable;
- @NonNull private final RestartAuthRunnable mRestartAuthRunnable;
-
- private static class TestableBiometricScheduler extends BiometricScheduler {
- @NonNull private Fingerprint21UdfpsMock mFingerprint21;
-
- TestableBiometricScheduler(
- @Nullable GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(BiometricScheduler.SENSOR_TYPE_FP_OTHER, gestureAvailabilityDispatcher);
- }
-
- void init(@NonNull Fingerprint21UdfpsMock fingerprint21) {
- mFingerprint21 = fingerprint21;
- }
- }
-
- /**
- * All of the mocking/testing should happen in here. This way we don't need to modify the
- * {@link BaseClientMonitor} implementations and can run the
- * real path there.
- */
- private static class MockHalResultController extends HalResultController {
-
- // Duration for which a sensor authentication can be treated as UDFPS success.
- private static final int AUTH_VALIDITY_MS = 10 * 1000; // 10 seconds
-
- static class LastAuthArgs {
- @NonNull final AuthenticationConsumer lastAuthenticatedClient;
- final long deviceId;
- final int fingerId;
- final int groupId;
- @Nullable final ArrayList<Byte> token;
-
- LastAuthArgs(@NonNull AuthenticationConsumer authenticationConsumer, long deviceId,
- int fingerId, int groupId, @Nullable ArrayList<Byte> token) {
- lastAuthenticatedClient = authenticationConsumer;
- this.deviceId = deviceId;
- this.fingerId = fingerId;
- this.groupId = groupId;
- if (token == null) {
- this.token = null;
- } else {
- // Store a copy so the owner can be GC'd
- this.token = new ArrayList<>(token);
- }
- }
- }
-
- // Initialized after the constructor, but before it's ever used.
- @NonNull private RestartAuthRunnable mRestartAuthRunnable;
- @NonNull private Fingerprint21UdfpsMock mFingerprint21;
- @Nullable private LastAuthArgs mLastAuthArgs;
-
- MockHalResultController(int sensorId, @NonNull Context context, @NonNull Handler handler,
- @NonNull BiometricScheduler scheduler) {
- super(sensorId, context, handler, scheduler);
- }
-
- void init(@NonNull RestartAuthRunnable restartAuthRunnable,
- @NonNull Fingerprint21UdfpsMock fingerprint21) {
- mRestartAuthRunnable = restartAuthRunnable;
- mFingerprint21 = fingerprint21;
- }
-
- @Nullable AuthenticationConsumer getLastAuthenticatedClient() {
- return mLastAuthArgs != null ? mLastAuthArgs.lastAuthenticatedClient : null;
- }
-
- /**
- * Intercepts the HAL authentication and holds it until the UDFPS simulation decides
- * that authentication finished.
- */
- @Override
- public void onAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- mHandler.post(() -> {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
- if (!(client instanceof AuthenticationConsumer)) {
- Slog.e(TAG, "Non authentication consumer: " + client);
- return;
- }
-
- final boolean accepted = fingerId != 0;
- if (accepted) {
- mFingerprint21.setDebugMessage("Finger accepted");
- } else {
- mFingerprint21.setDebugMessage("Finger rejected");
- }
-
- final AuthenticationConsumer authenticationConsumer =
- (AuthenticationConsumer) client;
- mLastAuthArgs = new LastAuthArgs(authenticationConsumer, deviceId, fingerId,
- groupId, token);
-
- // Remove any existing restart runnbables since auth just started, and put a new
- // one on the queue.
- mHandler.removeCallbacks(mRestartAuthRunnable);
- mRestartAuthRunnable.setLastAuthReference(authenticationConsumer);
- mHandler.postDelayed(mRestartAuthRunnable, AUTH_VALIDITY_MS);
- });
- }
-
- /**
- * Calls through to the real interface and notifies clients of accept/reject.
- */
- void sendAuthenticated(long deviceId, int fingerId, int groupId,
- ArrayList<Byte> token) {
- Slog.d(TAG, "sendAuthenticated: " + (fingerId != 0));
- mFingerprint21.setDebugMessage("Udfps match: " + (fingerId != 0));
- super.onAuthenticated(deviceId, fingerId, groupId, token);
- }
- }
-
- public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
- @NonNull BiometricContext biometricContext) {
- Slog.d(TAG, "Creating Fingerprint23Mock!");
-
- final Handler handler = new Handler(Looper.getMainLooper());
- final TestableBiometricScheduler scheduler =
- new TestableBiometricScheduler(gestureAvailabilityDispatcher);
- final MockHalResultController controller =
- new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, biometricStateCallback,
- authenticationStateListeners, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller, biometricContext);
- }
-
- private static abstract class FakeFingerRunnable implements Runnable {
- private long mFingerDownTime;
- private int mCaptureDuration;
-
- /**
- * @param fingerDownTime System time when onFingerDown occurred
- * @param captureDuration Duration that the finger needs to be down for
- */
- void setSimulationTime(long fingerDownTime, int captureDuration) {
- mFingerDownTime = fingerDownTime;
- mCaptureDuration = captureDuration;
- }
-
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean isImageCaptureComplete() {
- return System.currentTimeMillis() - mFingerDownTime > mCaptureDuration;
- }
- }
-
- private final class FakeRejectRunnable extends FakeFingerRunnable {
- @Override
- public void run() {
- mMockHalResultController.sendAuthenticated(0, 0, 0, null);
- }
- }
-
- private final class FakeAcceptRunnable extends FakeFingerRunnable {
- @Override
- public void run() {
- if (mMockHalResultController.mLastAuthArgs == null) {
- // This can happen if the user has trust agents enabled, which make lockscreen
- // dismissable. Send a fake non-zero (accept) finger.
- Slog.d(TAG, "Sending fake finger");
- mMockHalResultController.sendAuthenticated(1 /* deviceId */,
- 1 /* fingerId */, 1 /* groupId */, null /* token */);
- } else {
- mMockHalResultController.sendAuthenticated(
- mMockHalResultController.mLastAuthArgs.deviceId,
- mMockHalResultController.mLastAuthArgs.fingerId,
- mMockHalResultController.mLastAuthArgs.groupId,
- mMockHalResultController.mLastAuthArgs.token);
- }
- }
- }
-
- /**
- * The fingerprint HAL allows multiple (5) fingerprint attempts per HIDL invocation of the
- * authenticate method. However, valid fingerprint authentications are invalidated after
- * {@link MockHalResultController#AUTH_VALIDITY_MS}, meaning UDFPS touches will be reported as
- * rejects if touched after that duration. However, since a valid fingerprint was detected, the
- * HAL and FingerprintService will not look for subsequent fingerprints.
- *
- * In order to keep the FingerprintManager API consistent (that multiple fingerprint attempts
- * are allowed per auth lifecycle), we internally cancel and restart authentication so that the
- * sensor is responsive again.
- */
- private static final class RestartAuthRunnable implements Runnable {
- @NonNull private final Fingerprint21UdfpsMock mFingerprint21;
- @NonNull private final TestableBiometricScheduler mScheduler;
-
- // Store a reference to the auth consumer that should be invalidated.
- private AuthenticationConsumer mLastAuthConsumer;
-
- RestartAuthRunnable(@NonNull Fingerprint21UdfpsMock fingerprint21,
- @NonNull TestableBiometricScheduler scheduler) {
- mFingerprint21 = fingerprint21;
- mScheduler = scheduler;
- }
-
- void setLastAuthReference(AuthenticationConsumer lastAuthConsumer) {
- mLastAuthConsumer = lastAuthConsumer;
- }
-
- @Override
- public void run() {
- final BaseClientMonitor client = mScheduler.getCurrentClient();
-
- // We don't care about FingerprintDetectClient, since accept/rejects are both OK. UDFPS
- // rejects will just simulate the path where non-enrolled fingers are presented.
- if (!(client instanceof FingerprintAuthenticationClient)) {
- Slog.e(TAG, "Non-FingerprintAuthenticationClient client: " + client);
- return;
- }
-
- // Perhaps the runnable is stale, or the user stopped/started auth manually. Do not
- // restart auth in this case.
- if (client != mLastAuthConsumer) {
- Slog.e(TAG, "Current client: " + client
- + " does not match mLastAuthConsumer: " + mLastAuthConsumer);
- return;
- }
-
- Slog.d(TAG, "Restarting auth, current: " + client);
- mFingerprint21.setDebugMessage("Auth timed out");
-
- final FingerprintAuthenticationClient authClient =
- (FingerprintAuthenticationClient) client;
- // Store the authClient parameters so it can be rescheduled
- final IBinder token = client.getToken();
- final long operationId = authClient.getOperationId();
- final int cookie = client.getCookie();
- final ClientMonitorCallbackConverter listener = new ClientMonitorCallbackConverter(
- new IFingerprintServiceReceiver.Default());
- final boolean restricted = authClient.isRestricted();
- final int statsClient = client.getLogger().getStatsClient();
- final boolean isKeyguard = authClient.isKeyguard();
- final FingerprintAuthenticateOptions options =
- new FingerprintAuthenticateOptions.Builder()
- .setUserId(client.getTargetUserId())
- .setOpPackageName(client.getOwnerString())
- .build();
-
- // Don't actually send cancel() to the HAL, since successful auth already finishes
- // HAL authenticate() lifecycle. Just
- mScheduler.getInternalCallback().onClientFinished(client, true /* success */);
-
- // Schedule this only after we invoke onClientFinished for the previous client, so that
- // internal preemption logic is not run.
- mFingerprint21.scheduleAuthenticate(token,
- operationId, cookie, listener, options, restricted, statsClient,
- isKeyguard);
- }
- }
-
- private Fingerprint21UdfpsMock(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull TestableBiometricScheduler scheduler,
- @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull MockHalResultController controller,
- @NonNull BiometricContext biometricContext) {
- super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler,
- handler, lockoutResetDispatcher, controller, biometricContext);
- mScheduler = scheduler;
- mScheduler.init(this);
- mHandler = handler;
- // resetLockout is controlled by the framework, so hardwareAuthToken is not required
- final boolean resetLockoutRequiresHardwareAuthToken = false;
- final int maxTemplatesAllowed = mContext.getResources()
- .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser);
- mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId,
- sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */,
- resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations());
- mMockHalResultController = controller;
- mUserHasTrust = new SparseBooleanArray();
- mTrustManager = context.getSystemService(TrustManager.class);
- mTrustManager.registerTrustListener(this);
- mRandom = new Random();
- mFakeRejectRunnable = new FakeRejectRunnable();
- mFakeAcceptRunnable = new FakeAcceptRunnable();
- mRestartAuthRunnable = new RestartAuthRunnable(this, mScheduler);
-
- // We can't initialize this during MockHalresultController's constructor due to a circular
- // dependency.
- mMockHalResultController.init(mRestartAuthRunnable, this);
- }
-
- @Override
- public void onTrustChanged(boolean enabled, boolean newlyUnlocked, int userId, int flags,
- List<String> trustGrantedMessages) {
- mUserHasTrust.put(userId, enabled);
- }
-
- @Override
- public void onTrustManagedChanged(boolean enabled, int userId) {
-
- }
-
- @Override
- public void onTrustError(CharSequence message) {
-
- }
-
- @Override
- public void onEnabledTrustAgentsChanged(int userId) {
-
- }
-
- @Override
- public void onIsActiveUnlockRunningChanged(boolean isRunning, int userId) {
-
- }
-
- @Override
- @NonNull
- public List<FingerprintSensorPropertiesInternal> getSensorProperties() {
- final List<FingerprintSensorPropertiesInternal> properties = new ArrayList<>();
- properties.add(mSensorProperties);
- return properties;
- }
-
- @Override
- public void onPointerDown(long requestId, int sensorId, PointerContext pc) {
- mHandler.post(() -> {
- Slog.d(TAG, "onFingerDown");
- final AuthenticationConsumer lastAuthenticatedConsumer =
- mMockHalResultController.getLastAuthenticatedClient();
- final BaseClientMonitor currentScheduledClient = mScheduler.getCurrentClient();
-
- if (currentScheduledClient == null) {
- Slog.d(TAG, "Not authenticating");
- return;
- }
-
- mHandler.removeCallbacks(mFakeRejectRunnable);
- mHandler.removeCallbacks(mFakeAcceptRunnable);
-
- // The sensor was authenticated, is still the currently scheduled client, and the
- // user touched the UDFPS affordance. Pretend that auth succeeded.
- final boolean authenticatedClientIsCurrent = lastAuthenticatedConsumer != null
- && lastAuthenticatedConsumer == currentScheduledClient;
- // User is unlocked on keyguard via Trust Agent
- final boolean keyguardAndTrusted;
- if (currentScheduledClient instanceof FingerprintAuthenticationClient) {
- keyguardAndTrusted = ((FingerprintAuthenticationClient) currentScheduledClient)
- .isKeyguard()
- && mUserHasTrust.get(currentScheduledClient.getTargetUserId(), false);
- } else {
- keyguardAndTrusted = false;
- }
-
- final int captureDuration = getNewCaptureDuration();
- final int matchingDuration = getMatchingDuration();
- final int totalDuration = captureDuration + matchingDuration;
- setDebugMessage("Duration: " + totalDuration
- + " (" + captureDuration + " + " + matchingDuration + ")");
- if (authenticatedClientIsCurrent || keyguardAndTrusted) {
- mFakeAcceptRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
- mHandler.postDelayed(mFakeAcceptRunnable, totalDuration);
- } else if (currentScheduledClient instanceof AuthenticationConsumer) {
- // Something is authenticating but authentication has not succeeded yet. Pretend
- // that auth rejected.
- mFakeRejectRunnable.setSimulationTime(System.currentTimeMillis(), captureDuration);
- mHandler.postDelayed(mFakeRejectRunnable, totalDuration);
- }
- });
- }
-
- @Override
- public void onPointerUp(long requestId, int sensorId, PointerContext pc) {
- mHandler.post(() -> {
- Slog.d(TAG, "onFingerUp");
-
- // Only one of these can be on the handler at any given time (see onFingerDown). If
- // image capture is not complete, send ACQUIRED_TOO_FAST and remove the runnable from
- // the handler. Image capture (onFingerDown) needs to happen again.
- if (mHandler.hasCallbacks(mFakeRejectRunnable)
- && !mFakeRejectRunnable.isImageCaptureComplete()) {
- mHandler.removeCallbacks(mFakeRejectRunnable);
- mMockHalResultController.onAcquired(0 /* deviceId */,
- FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
- 0 /* vendorCode */);
- } else if (mHandler.hasCallbacks(mFakeAcceptRunnable)
- && !mFakeAcceptRunnable.isImageCaptureComplete()) {
- mHandler.removeCallbacks(mFakeAcceptRunnable);
- mMockHalResultController.onAcquired(0 /* deviceId */,
- FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST,
- 0 /* vendorCode */);
- }
- });
- }
-
- private int getNewCaptureDuration() {
- final ContentResolver contentResolver = mContext.getContentResolver();
- final int captureTime = Settings.Secure.getIntForUser(contentResolver,
- CONFIG_AUTH_DELAY_PT1,
- DEFAULT_AUTH_DELAY_PT1_MS,
- UserHandle.USER_CURRENT);
- final int randomDelayRange = Settings.Secure.getIntForUser(contentResolver,
- CONFIG_AUTH_DELAY_RANDOMNESS,
- DEFAULT_AUTH_DELAY_RANDOMNESS_MS,
- UserHandle.USER_CURRENT);
- final int randomDelay = mRandom.nextInt(randomDelayRange * 2) - randomDelayRange;
-
- // Must be at least 0
- return Math.max(captureTime + randomDelay, 0);
- }
-
- private int getMatchingDuration() {
- final int matchingTime = Settings.Secure.getIntForUser(mContext.getContentResolver(),
- CONFIG_AUTH_DELAY_PT2,
- DEFAULT_AUTH_DELAY_PT2_MS,
- UserHandle.USER_CURRENT);
-
- // Must be at least 0
- return Math.max(matchingTime, 0);
- }
-
- private void setDebugMessage(String message) {
- try {
- final IUdfpsOverlayController controller = getUdfpsOverlayController();
- // Things can happen before SysUI loads and sets the controller.
- if (controller != null) {
- Slog.d(TAG, "setDebugMessage: " + message);
- controller.setDebugMessage(mSensorProperties.sensorId, message);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending message: " + message, e);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
deleted file mode 100644
index b6311af..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.TaskStackListener;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.BiometricSourceType;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.log.CallbackWithProbe;
-import com.android.server.biometrics.log.Probe;
-import com.android.server.biometrics.sensors.AuthenticationClient;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific authentication client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintAuthenticationClient
- extends AuthenticationClient<IBiometricsFingerprint, FingerprintAuthenticateOptions>
- implements Udfps {
-
- private static final String TAG = "Biometrics/FingerprintAuthClient";
-
- private final LockoutFrameworkImpl mLockoutFrameworkImpl;
- @NonNull private final SensorOverlays mSensorOverlays;
- @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
- @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
-
- private boolean mIsPointerDown;
-
- FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, long operationId,
- boolean restricted, @NonNull FingerprintAuthenticateOptions options,
- int cookie, boolean requireConfirmation, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, boolean isStrongBiometric,
- @NonNull TaskStackListener taskStackListener,
- @NonNull LockoutFrameworkImpl lockoutTracker,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable ISidefpsController sidefpsController,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @Authenticators.Types int sensorStrength) {
- super(context, lazyDaemon, token, listener, operationId, restricted,
- options, cookie, requireConfirmation, logger, biometricContext,
- isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- false /* shouldVibrate */, sensorStrength);
- setRequestId(requestId);
- mLockoutFrameworkImpl = lockoutTracker;
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
- }
- mAuthenticationStateListeners = authenticationStateListeners;
- mSensorProps = sensorProps;
- mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- if (mSensorProps.isAnyUdfpsType()) {
- // UDFPS requires user to touch before becoming "active"
- mState = STATE_STARTED_PAUSED;
- } else {
- mState = STATE_STARTED;
- }
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback, callback);
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
-
- // Authentication lifecycle ends either when
- // 1) Authenticated == true
- // 2) Error occurred (lockout or some other error)
- // Note that authentication doesn't end when Authenticated == false
-
- if (authenticated) {
- mState = STATE_STOPPED;
- resetFailedAttempts(getTargetUserId());
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- if (reportBiometricAuthAttempts()) {
- mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
- getTargetUserId());
- }
- } else {
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
- final @LockoutTracker.LockoutMode int lockoutMode =
- mLockoutFrameworkImpl.getLockoutModeForUser(getTargetUserId());
- if (lockoutMode != LockoutTracker.LOCKOUT_NONE) {
- Slog.w(TAG, "Fingerprint locked out, lockoutMode(" + lockoutMode + ")");
- final int errorCode = lockoutMode == LockoutTracker.LOCKOUT_TIMED
- ? BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- : BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
- // Send the error, but do not invoke the FinishCallback yet. Since lockout is not
- // controlled by the HAL, the framework must stop the sensor before finishing the
- // client.
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
- cancel();
- }
- if (reportBiometricAuthAttempts()) {
- mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
- getTargetUserId());
- }
- }
- }
-
- @Override
- public void onError(int errorCode, int vendorCode) {
- super.onError(errorCode, vendorCode);
-
- if (errorCode == BiometricFingerprintConstants.FINGERPRINT_ERROR_BAD_CALIBRATION) {
- BiometricNotificationUtils.showBadCalibrationNotification(getContext());
- }
-
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
-
- private void resetFailedAttempts(int userId) {
- mLockoutFrameworkImpl.resetFailedAttemptsForUser(true /* clearAttemptCounter */, userId);
- }
-
- @Override
- protected void handleLifecycleAfterAuth(boolean authenticated) {
- if (authenticated) {
- mCallback.onClientFinished(this, true /* success */);
- }
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
- mAuthenticationStateListeners.onAuthenticationAcquired(
- BiometricSourceType.FINGERPRINT, getRequestReason(), acquiredInfo);
- super.onAcquired(acquiredInfo, vendorCode);
-
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(getTargetUserId());
- if (lockoutMode == LockoutTracker.LOCKOUT_NONE) {
- PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
- }
- }
-
- @Override
- public boolean wasUserDetected() {
- // TODO: Update if it needs to be used for fingerprint, i.e. success/reject, error_timeout
- return false;
- }
-
- @Override
- public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
- mLockoutFrameworkImpl.addFailedAttemptForUser(userId);
- @LockoutTracker.LockoutMode final int lockoutMode =
- getLockoutTracker().getLockoutModeForUser(userId);
- final PerformanceTracker performanceTracker =
- PerformanceTracker.getInstanceForSensorId(getSensorId());
- if (lockoutMode == LockoutTracker.LOCKOUT_PERMANENT) {
- performanceTracker.incrementPermanentLockoutForUser(userId);
- } else if (lockoutMode == LockoutTracker.LOCKOUT_TIMED) {
- performanceTracker.incrementTimedLockoutForUser(userId);
- }
-
- return lockoutMode;
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getRequestReason(), this);
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
- }
-
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().authenticate(mOperationId, getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- mState = STATE_STARTED;
- mALSProbeCallback.getProbe().enable();
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
-
- try {
- getListener().onUdfpsPointerDown(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
- mALSProbeCallback.getProbe().disable();
- UdfpsHelper.onFingerUp(getFreshDaemon());
-
- try {
- getListener().onUdfpsPointerUp(getSensorId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
deleted file mode 100644
index 50e48fe..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricRequestConstants;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintAuthenticateOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AcquisitionClient;
-import com.android.server.biometrics.sensors.AuthenticationConsumer;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.PerformanceTracker;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Performs fingerprint detection without exposing any matching information (e.g. accept/reject
- * have the same haptic, lockout counter is not increased).
- */
-class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
- implements AuthenticationConsumer, Udfps {
-
- private static final String TAG = "FingerprintDetectClient";
-
- private final boolean mIsStrongBiometric;
- @NonNull private final SensorOverlays mSensorOverlays;
- private boolean mIsPointerDown;
-
- public FingerprintDetectClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener,
- @NonNull FingerprintAuthenticateOptions options,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- boolean isStrongBiometric) {
- super(context, lazyDaemon, token, listener, options.getUserId(),
- options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
- true /* shouldVibrate */, biometricLogger, biometricContext);
- setRequestId(requestId);
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(
- udfpsOverlayController, null /* sideFpsController */);
- }
- mIsStrongBiometric = isStrongBiometric;
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- startHalOperation();
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
- this);
-
- try {
- getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting auth", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- UdfpsHelper.onFingerUp(getFreshDaemon());
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-
- @Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
- getLogger().logOnAuthenticated(getContext(), getOperationContext(),
- authenticated, false /* requireConfirmation */,
- getTargetUserId(), false /* isBiometricPrompt */);
-
- // Do not distinguish between success/failures.
- vibrateSuccess();
-
- final PerformanceTracker pm = PerformanceTracker.getInstanceForSensorId(getSensorId());
- pm.incrementAuthForUser(getTargetUserId(), authenticated);
-
- try {
- getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when sending onDetected", e);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_DETECT_INTERACTION;
- }
-
- @Override
- public boolean interruptsPrecedingClients() {
- return true;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
deleted file mode 100644
index 8f937fc..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.fingerprint.PointerContext;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintEnrollOptions;
-import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.ISidefpsController;
-import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricNotificationUtils;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
-import com.android.server.biometrics.sensors.EnrollClient;
-import com.android.server.biometrics.sensors.SensorOverlays;
-import com.android.server.biometrics.sensors.fingerprint.Udfps;
-import com.android.server.biometrics.sensors.fingerprint.UdfpsHelper;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific enroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint>
- implements Udfps {
-
- private static final String TAG = "FingerprintEnrollClient";
-
- @NonNull private final SensorOverlays mSensorOverlays;
- private final @FingerprintManager.EnrollReason int mEnrollReason;
- @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
- private boolean mIsPointerDown;
-
- FingerprintEnrollClient(
- @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int userId,
- @NonNull byte[] hardwareAuthToken, @NonNull String owner,
- @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController,
- // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
- @Nullable ISidefpsController sidefpsController,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintEnrollOptions options) {
- super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
- biometricContext,
- BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason()));
- setRequestId(requestId);
- if (sidefpsControllerRefactor()) {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController);
- } else {
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
- }
- mAuthenticationStateListeners = authenticationStateListeners;
-
- mEnrollReason = enrollReason;
- if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
- getLogger().disableMetrics();
- }
- Slog.w(TAG, "EnrollOptions "
- + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason()));
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- BiometricNotificationUtils.cancelFingerprintEnrollNotification(getContext());
- }
-
- @NonNull
- @Override
- protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(
- getLogger().getAmbientLightProbe(true /* startWithClient */), callback);
- }
-
- @Override
- protected boolean hasReachedEnrollmentLimit() {
- final int limit = getContext().getResources().getInteger(
- com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser);
- final int enrolled = mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId())
- .size();
- if (enrolled >= limit) {
- Slog.w(TAG, "Too many fingerprints registered, user: " + getTargetUserId());
- return true;
- }
- return false;
- }
-
- @Override
- protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
- this);
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStarted(
- getRequestReasonFromEnrollReason(mEnrollReason));
- }
-
- BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().enroll(mHardwareAuthToken, getTargetUserId(), mTimeoutSec);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enroll", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- protected void stopHalOperation() {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
-
- try {
- getFreshDaemon().cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting cancel", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
- super.onEnrollResult(identifier, remaining);
-
- mSensorOverlays.ifUdfps(
- controller -> controller.onEnrollmentProgress(getSensorId(), remaining));
-
- if (remaining == 0) {
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
- }
-
- @Override
- public void onAcquired(int acquiredInfo, int vendorCode) {
- super.onAcquired(acquiredInfo, vendorCode);
-
- mSensorOverlays.ifUdfps(controller -> {
- if (UdfpsHelper.isValidAcquisitionMessage(getContext(), acquiredInfo, vendorCode)) {
- controller.onEnrollmentHelp(getSensorId());
- }
- });
-
- mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
- }
-
- @Override
- public void onError(int errorCode, int vendorCode) {
- super.onError(errorCode, vendorCode);
-
- mSensorOverlays.hide(getSensorId());
- if (sidefpsControllerRefactor()) {
- mAuthenticationStateListeners.onAuthenticationStopped();
- }
- }
-
- @Override
- public void onPointerDown(PointerContext pc) {
- mIsPointerDown = true;
- UdfpsHelper.onFingerDown(getFreshDaemon(), (int) pc.x, (int) pc.y, pc.minor, pc.major);
- }
-
- @Override
- public void onPointerUp(PointerContext pc) {
- mIsPointerDown = false;
- UdfpsHelper.onFingerUp(getFreshDaemon());
- }
-
- @Override
- public boolean isPointerDown() {
- return mIsPointerDown;
- }
-
- @Override
- public void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event) {
- // Unsupported in HIDL.
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
deleted file mode 100644
index 3bb7135..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintGenerateChallengeClient.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.GenerateChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific generateChallenge/preEnroll client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintGenerateChallengeClient
- extends GenerateChallengeClient<IBiometricsFingerprint> {
-
- private static final String TAG = "FingerprintGenerateChallengeClient";
-
- FingerprintGenerateChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, listener, userId, owner, sensorId, logger,
- biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final long challenge = getFreshDaemon().preEnroll();
- try {
- getListener().onChallengeGenerated(getSensorId(), getTargetUserId(), challenge);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "preEnroll failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
deleted file mode 100644
index 8b61f59..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalCleanupClient.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalCleanupClient;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal cleanup client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalCleanupClient
- extends InternalCleanupClient<Fingerprint, IBiometricsFingerprint> {
-
- FingerprintInternalCleanupClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull BiometricUtils<Fingerprint> utils,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, userId, owner, sensorId, logger, biometricContext,
- utils, authenticatorIds);
- }
-
- @Override
- protected InternalEnumerateClient<IBiometricsFingerprint> getEnumerateClient(
- Context context, Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
- int userId, String owner, List<Fingerprint> enrolledList,
- BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- return new FingerprintInternalEnumerateClient(context, lazyDaemon, token, userId, owner,
- enrolledList, utils, sensorId, logger, biometricContext);
- }
-
- @Override
- protected RemovalClient<Fingerprint, IBiometricsFingerprint> getRemovalClient(Context context,
- Supplier<IBiometricsFingerprint> lazyDaemon, IBinder token,
- int biometricId, int userId, String owner, BiometricUtils<Fingerprint> utils,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext, Map<Integer, Long> authenticatorIds) {
- // Internal remove does not need to send results to anyone. Cleanup (enumerate + remove)
- // is all done internally.
- return new FingerprintRemovalClient(context, lazyDaemon, token,
- null /* ClientMonitorCallbackConverter */, biometricId, userId, owner, utils,
- sensorId, logger, biometricContext, authenticatorIds);
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
deleted file mode 100644
index 0840f1b..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintInternalEnumerateClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.InternalEnumerateClient;
-
-import java.util.List;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific internal enumerate client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintInternalEnumerateClient extends InternalEnumerateClient<IBiometricsFingerprint> {
- private static final String TAG = "FingerprintInternalEnumerateClient";
-
- FingerprintInternalEnumerateClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, @NonNull List<Fingerprint> enrolledList,
- @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, enrolledList, utils, sensorId,
- logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().enumerate();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting enumerate", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
deleted file mode 100644
index 9ec56c2..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRemovalClient.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.Fingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BiometricUtils;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-import com.android.server.biometrics.sensors.RemovalClient;
-
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific removal client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-class FingerprintRemovalClient extends RemovalClient<Fingerprint, IBiometricsFingerprint> {
- private static final String TAG = "FingerprintRemovalClient";
-
- private final int mBiometricId;
-
- FingerprintRemovalClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- @NonNull ClientMonitorCallbackConverter listener, int biometricId, int userId,
- @NonNull String owner, @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Map<Integer, Long> authenticatorIds) {
- super(context, lazyDaemon, token, listener, userId, owner, utils, sensorId,
- logger, biometricContext, authenticatorIds);
- mBiometricId = biometricId;
- }
-
- @Override
- protected void startHalOperation() {
- try {
- // GroupId was never used. In fact, groupId is always the same as userId.
- getFreshDaemon().remove(getTargetUserId(), mBiometricId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when requesting remove", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
deleted file mode 100644
index 843fcc8..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintResetLockoutClient.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.BaseClientMonitor;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-
-/**
- * Clears lockout, which is handled in the framework (and not the HAL) for the
- * IBiometricsFingerprint@2.1 interface.
- */
-public class FingerprintResetLockoutClient extends BaseClientMonitor {
-
- @NonNull final LockoutFrameworkImpl mLockoutTracker;
-
- public FingerprintResetLockoutClient(@NonNull Context context, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull LockoutFrameworkImpl lockoutTracker) {
- super(context, null /* token */, null /* listener */, userId, owner, 0 /* cookie */,
- sensorId, logger, biometricContext);
- mLockoutTracker = lockoutTracker;
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- mLockoutTracker.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
- getTargetUserId());
- callback.onClientFinished(this, true /* success */);
- }
-
- public boolean interruptsPrecedingClients() {
- return true;
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_RESET_LOCKOUT;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
deleted file mode 100644
index 6273417..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintRevokeChallengeClient.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.RevokeChallengeClient;
-
-import java.util.function.Supplier;
-
-/**
- * Fingerprint-specific revokeChallenge client supporting the
- * {@link android.hardware.biometrics.fingerprint.V2_1} and
- * {@link android.hardware.biometrics.fingerprint.V2_2} HIDL interfaces.
- */
-public class FingerprintRevokeChallengeClient
- extends RevokeChallengeClient<IBiometricsFingerprint> {
-
- private static final String TAG = "FingerprintRevokeChallengeClient";
-
- FingerprintRevokeChallengeClient(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
- int userId, @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
- super(context, lazyDaemon, token, userId, owner, sensorId, logger, biometricContext);
- }
-
- @Override
- protected void startHalOperation() {
- try {
- getFreshDaemon().postEnroll();
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "revokeChallenge/postEnroll failed", e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
deleted file mode 100644
index fc85402..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintUpdateActiveUserClientLegacy.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors.fingerprint.hidl;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.os.Build;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SELinux;
-import android.util.Slog;
-
-import com.android.server.biometrics.BiometricsProto;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.HalClientMonitor;
-
-import java.io.File;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * TODO(b/304604965): Delete this class once Flags.DE_HIDL is ready for release.
- */
-public class FingerprintUpdateActiveUserClientLegacy extends
- HalClientMonitor<IBiometricsFingerprint> {
- private static final String TAG = "FingerprintUpdateActiveUserClient";
- private static final String FP_DATA_DIR = "fpdata";
-
- private final Supplier<Integer> mCurrentUserId;
- private final boolean mForceUpdateAuthenticatorId;
- private final boolean mHasEnrolledBiometrics;
- private final Map<Integer, Long> mAuthenticatorIds;
- private File mDirectory;
-
- FingerprintUpdateActiveUserClientLegacy(@NonNull Context context,
- @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, int userId,
- @NonNull String owner, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- @NonNull Supplier<Integer> currentUserId,
- boolean hasEnrolledBiometrics, @NonNull Map<Integer, Long> authenticatorIds,
- boolean forceUpdateAuthenticatorId) {
- super(context, lazyDaemon, null /* token */, null /* listener */, userId, owner,
- 0 /* cookie */, sensorId, logger, biometricContext);
- mCurrentUserId = currentUserId;
- mForceUpdateAuthenticatorId = forceUpdateAuthenticatorId;
- mHasEnrolledBiometrics = hasEnrolledBiometrics;
- mAuthenticatorIds = authenticatorIds;
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- if (mCurrentUserId.get() == getTargetUserId() && !mForceUpdateAuthenticatorId) {
- Slog.d(TAG, "Already user: " + mCurrentUserId + ", returning");
- callback.onClientFinished(this, true /* success */);
- return;
- }
-
- int firstSdkInt = Build.VERSION.DEVICE_INITIAL_SDK_INT;
- if (firstSdkInt < Build.VERSION_CODES.BASE) {
- Slog.e(TAG, "First SDK version " + firstSdkInt + " is invalid; must be "
- + "at least VERSION_CODES.BASE");
- }
- File baseDir;
- if (firstSdkInt <= Build.VERSION_CODES.O_MR1) {
- baseDir = Environment.getUserSystemDirectory(getTargetUserId());
- } else {
- baseDir = Environment.getDataVendorDeDirectory(getTargetUserId());
- }
-
- mDirectory = new File(baseDir, FP_DATA_DIR);
- if (!mDirectory.exists()) {
- if (!mDirectory.mkdir()) {
- Slog.e(TAG, "Cannot make directory: " + mDirectory.getAbsolutePath());
- callback.onClientFinished(this, false /* success */);
- return;
- }
- // Calling mkdir() from this process will create a directory with our
- // permissions (inherited from the containing dir). This command fixes
- // the label.
- if (!SELinux.restorecon(mDirectory)) {
- Slog.e(TAG, "Restorecons failed. Directory will have wrong label.");
- callback.onClientFinished(this, false /* success */);
- return;
- }
- }
-
- startHalOperation();
- }
-
- @Override
- public void unableToStart() {
- // Nothing to do here
- }
-
- @Override
- protected void startHalOperation() {
- try {
- final int targetId = getTargetUserId();
- Slog.d(TAG, "Setting active user: " + targetId);
- getFreshDaemon().setActiveGroup(targetId, mDirectory.getAbsolutePath());
- mAuthenticatorIds.put(targetId, mHasEnrolledBiometrics
- ? getFreshDaemon().getAuthenticatorId() : 0L);
- mCallback.onClientFinished(this, true /* success */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to setActiveGroup: " + e);
- mCallback.onClientFinished(this, false /* success */);
- }
- }
-
- @Override
- public int getProtoEnum() {
- return BiometricsProto.CM_UPDATE_ACTIVE_USER;
- }
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 47fdcdb..3214b6d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -186,7 +186,7 @@
mLockoutTracker,
mLockoutResetDispatcher,
mAuthSessionCoordinator,
- () -> {}, mAidlResponseHandlerCallback);
+ mAidlResponseHandlerCallback);
}
@VisibleForTesting IBiometricsFingerprint getIBiometricsFingerprint() {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 05e681e..645a366 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -61,6 +61,9 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -69,6 +72,7 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Range;
import android.util.Slog;
import android.view.Display;
import android.view.IDisplayWindowListener;
@@ -85,6 +89,8 @@
import com.android.server.SystemService;
import com.android.server.wm.WindowManagerInternal;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -243,6 +249,7 @@
public int mVideoStabilizationMode;
public boolean mUsedUltraWide;
public boolean mUsedZoomOverride;
+ public Range<Integer> mMostRequestedFpsRange;
public final long mLogId;
public final int mSessionIndex;
@@ -265,13 +272,15 @@
mDeviceError = deviceError;
mLogId = logId;
mSessionIndex = sessionIdx;
+ mMostRequestedFpsRange = new Range<Integer>(0, 0);
}
public void markCompleted(int internalReconfigure, long requestCount,
long resultErrorCount, boolean deviceError,
List<CameraStreamStats> streamStats, String userTag,
int videoStabilizationMode, boolean usedUltraWide,
- boolean usedZoomOverride, CameraExtensionSessionStats extStats) {
+ boolean usedZoomOverride, Range<Integer> mostRequestedFpsRange,
+ CameraExtensionSessionStats extStats) {
if (mCompleted) {
return;
}
@@ -287,6 +296,7 @@
mUsedUltraWide = usedUltraWide;
mUsedZoomOverride = usedZoomOverride;
mExtSessionStats = extStats;
+ mMostRequestedFpsRange = mostRequestedFpsRange;
if (CameraServiceProxy.DEBUG) {
Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
" was in use by " + mClientName + " for " +
@@ -637,6 +647,60 @@
Binder.restoreCallingIdentity(ident);
}
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ new CSPShellCmd(CameraServiceProxy.this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ private static class CSPShellCmd extends ShellCommand {
+ private static final String TAG = "CSPShellCmd";
+ private static final String USAGE = """
+ usage: cmd media.camera.proxy SUBCMD [args]
+
+ SUBCMDs:
+ dump_events: Write out all collected camera usage events to statsd.
+ Does not print to terminal.
+ help: You're reading it.
+ """;
+
+ private final CameraServiceProxy mCameraServiceProxy;
+
+ CSPShellCmd(CameraServiceProxy proxy) {
+ mCameraServiceProxy = proxy;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd.replace('-', '_')) {
+ case "dump_events":
+ int eventCount = mCameraServiceProxy.getUsageEventCount();
+ mCameraServiceProxy.dumpUsageEvents();
+ pw.println("Camera usage events dumped: " + eventCount);
+ break;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ Slog.e(mCameraServiceProxy.TAG, "Error running shell command", e);
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ getOutPrintWriter().println(USAGE);
+ }
+ }
};
private final FoldStateListener mFoldStateListener;
@@ -882,6 +946,9 @@
? ", zoomOverrideUsage " + e.mUsedZoomOverride
: "";
+ String mostRequestedFpsRangeDebug = Flags.analytics24q3()
+ ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange
+ : "";
Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction
+ " clientName " + e.mClientName
+ ", duration " + e.getDuration()
@@ -900,6 +967,7 @@
+ ", videoStabilizationMode " + e.mVideoStabilizationMode
+ ultrawideDebug
+ zoomOverrideDebug
+ + mostRequestedFpsRangeDebug
+ ", logId " + e.mLogId
+ ", sessionIndex " + e.mSessionIndex
+ ", mExtSessionStats {type " + extensionType
@@ -966,7 +1034,17 @@
e.mUserTag, e.mVideoStabilizationMode,
e.mLogId, e.mSessionIndex,
extensionType, extensionIsAdvanced, e.mUsedUltraWide,
- e.mUsedZoomOverride);
+ e.mUsedZoomOverride,
+ e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper());
+ }
+ }
+
+ /**
+ * Get camera usage event count
+ */
+ int getUsageEventCount() {
+ synchronized (mLock) {
+ return mCameraUsageHistory.size();
}
}
@@ -1173,6 +1251,10 @@
long logId = cameraState.getLogId();
int sessionIdx = cameraState.getSessionIndex();
CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
+ Range<Integer> mostRequestedFpsRange = Flags.analytics24q3()
+ ? cameraState.getMostRequestedFpsRange()
+ : new Range<Integer>(0, 0);
+
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1228,7 +1310,8 @@
oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
/*resultErrorCount*/0, /*deviceError*/false, streamStats,
/*userTag*/"", /*videoStabilizationMode*/-1, /*usedUltraWide*/false,
- /*usedZoomOverride*/false, new CameraExtensionSessionStats());
+ /*usedZoomOverride*/false, new Range<Integer>(0, 0),
+ new CameraExtensionSessionStats());
mCameraUsageHistory.add(oldEvent);
}
break;
@@ -1240,7 +1323,7 @@
doneEvent.markCompleted(internalReconfigureCount, requestCount,
resultErrorCount, deviceError, streamStats, userTag,
videoStabilizationMode, usedUltraWide, usedZoomOverride,
- extSessionStats);
+ mostRequestedFpsRange, extSessionStats);
mCameraUsageHistory.add(doneEvent);
// Do not double count device error
deviceError = false;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f32c11d..73df594 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -250,8 +250,19 @@
private final Object mAssociationsLock = new Object();
@GuardedBy("mAssociationsLock")
private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();
+
+ // The associations of input devices to displays by port. Maps from {InputDevice#mName} (String)
+ // 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
+ // input device go to a specific display.
+ @GuardedBy("mAssociationsLock")
+ private final Map<String, String> mUniqueIdAssociationsByDescriptor = new ArrayMap<>();
+
// The map from input port (String) to the keyboard layout identifiers (comma separated string
// containing language tag and layout type) associated with the corresponding keyboard device.
// Currently only accessed by InputReader.
@@ -1741,8 +1752,8 @@
/**
* Add a runtime association between the input port and the display port. This overrides any
* static associations.
- * @param inputPort The port of the input device.
- * @param displayPort The physical port of the associated display.
+ * @param inputPort the port of the input device
+ * @param displayPort the physical port of the associated display
*/
@Override // Binder call
public void addPortAssociation(@NonNull String inputPort, int displayPort) {
@@ -1763,7 +1774,7 @@
/**
* Remove the runtime association between the input port and the display port. Any existing
* static association for the cleared input port will be restored.
- * @param inputPort The port of the input device to be cleared.
+ * @param inputPort the port of the input device to be cleared
*/
@Override // Binder call
public void removePortAssociation(@NonNull String inputPort) {
@@ -1782,10 +1793,11 @@
}
@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()")) {
+ "addUniqueIdAssociationByPort()")) {
throw new SecurityException(
"Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
@@ -1793,22 +1805,65 @@
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()")) {
+ "removeUniqueIdAssociationByPort()")) {
throw new SecurityException("Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
}
Objects.requireNonNull(inputPort);
synchronized (mAssociationsLock) {
- mUniqueIdAssociations.remove(inputPort);
+ mUniqueIdAssociationsByPort.remove(inputPort);
+ }
+ mNative.changeUniqueIdAssociation();
+ }
+
+ /**
+ * Adds a runtime association between the input device descriptor and the display unique id.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ * @param displayUniqueId the unique ID of the display
+ */
+ @Override // Binder call
+ public void addUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor,
+ @NonNull String displayUniqueId) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+ "addUniqueIdAssociationByDescriptor()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+ }
+
+ Objects.requireNonNull(inputDeviceDescriptor);
+ Objects.requireNonNull(displayUniqueId);
+ synchronized (mAssociationsLock) {
+ mUniqueIdAssociationsByDescriptor.put(inputDeviceDescriptor, displayUniqueId);
+ }
+ mNative.changeUniqueIdAssociation();
+ }
+
+ /**
+ * Removes the runtime association between the input device and the display.
+ * @param inputDeviceDescriptor the descriptor of the input device
+ */
+ @Override // Binder call
+ public void removeUniqueIdAssociationByDescriptor(@NonNull String inputDeviceDescriptor) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY,
+ "removeUniqueIdAssociationByDescriptor()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY permission");
+ }
+
+ Objects.requireNonNull(inputDeviceDescriptor);
+ synchronized (mAssociationsLock) {
+ mUniqueIdAssociationsByDescriptor.remove(inputDeviceDescriptor);
}
mNative.changeUniqueIdAssociation();
}
@@ -2183,13 +2238,20 @@
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);
});
}
+ if (!mUniqueIdAssociationsByDescriptor.isEmpty()) {
+ pw.println("Unique Id Associations:");
+ mUniqueIdAssociationsByDescriptor.forEach((k, v) -> {
+ pw.print(" descriptor: " + k);
+ pw.println(" uniqueId: " + v);
+ });
+ }
if (!mDeviceTypeAssociations.isEmpty()) {
pw.println("Type Associations:");
mDeviceTypeAssociations.forEach((k, v) -> {
@@ -2622,10 +2684,21 @@
// 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);
+ }
+
+ // Native callback
+ @SuppressWarnings("unused")
+ private String[] getInputUniqueIdAssociationsByDescriptor() {
+ final Map<String, String> associations;
+ synchronized (mAssociationsLock) {
+ associations = new HashMap<>(mUniqueIdAssociationsByDescriptor);
}
return flatten(associations);
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index c7b60da..dd6433d 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -19,11 +19,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -67,7 +69,7 @@
AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
}
- static void initialize(@NonNull Handler handler) {
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
final UserManagerInternal userManagerInternal =
LocalServices.getService(UserManagerInternal.class);
handler.post(() -> {
@@ -79,8 +81,16 @@
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
- sPerUserMap.put(userId,
- AdditionalSubtypeUtils.load(userId));
+ final AdditionalSubtypeMap additionalSubtypeMap =
+ AdditionalSubtypeUtils.load(userId);
+ sPerUserMap.put(userId, additionalSubtypeMap);
+ final InputMethodSettings settings =
+ InputMethodManagerService
+ .queryInputMethodServicesInternal(context,
+ userId,
+ additionalSubtypeMap,
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 03a85c4..2583d73 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -283,9 +283,12 @@
final Context mContext;
final Resources mRes;
private final Handler mHandler;
- @NonNull
+
@MultiUserUnawareField
- private InputMethodSettings mSettings;
+ @UserIdInt
+ @GuardedBy("ImfLock.class")
+ private int mCurrentUserId;
+
@MultiUserUnawareField
final SettingsObserver mSettingsObserver;
final WindowManagerInternal mWindowManagerInternal;
@@ -490,7 +493,7 @@
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
- return mSettings.getMethodMap().get(imeId);
+ return InputMethodSettingsRepository.get(mCurrentUserId).getMethodMap().get(imeId);
}
/**
@@ -811,7 +814,8 @@
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
- String newEnabled = mSettings.getEnabledInputMethodsStr();
+ String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getEnabledInputMethodsStr();
if (!mLastEnabled.equals(newEnabled)) {
mLastEnabled = newEnabled;
enabledChanged = true;
@@ -843,9 +847,11 @@
// sender userId can be a real user ID or USER_ALL.
final int senderUserId = pendingResult.getSendingUserId();
if (senderUserId != UserHandle.USER_ALL) {
- if (senderUserId != mSettings.getUserId()) {
- // A background user is trying to hide the dialog. Ignore.
- return;
+ synchronized (ImfLock.class) {
+ if (senderUserId != mCurrentUserId) {
+ // A background user is trying to hide the dialog. Ignore.
+ return;
+ }
}
}
mMenuController.hideInputMethodMenu();
@@ -869,9 +875,14 @@
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
+ for (int userId : mUserManagerInternal.getUserIds()) {
+ final InputMethodSettings settings = queryInputMethodServicesInternal(
+ mContext,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, settings);
+ }
postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
@@ -932,7 +943,7 @@
@GuardedBy("ImfLock.class")
private boolean isChangingPackagesOfCurrentUserLocked() {
final int userId = getChangingUserId();
- final boolean retval = userId == mSettings.getUserId();
+ final boolean retval = userId == mCurrentUserId;
if (DEBUG) {
if (!retval) {
Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
@@ -947,8 +958,10 @@
if (!isChangingPackagesOfCurrentUserLocked()) {
return false;
}
- String curInputMethodId = mSettings.getSelectedInputMethod();
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
+ String curInputMethodId = settings.getSelectedInputMethod();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
final int numImes = methodList.size();
if (curInputMethodId != null) {
for (int i = 0; i < numImes; i++) {
@@ -1065,16 +1078,10 @@
private void onFinishPackageChangesInternal() {
synchronized (ImfLock.class) {
final int userId = getChangingUserId();
- final boolean isCurrentUser = (userId == mSettings.getUserId());
+ final boolean isCurrentUser = (userId == mCurrentUserId);
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings;
- if (isCurrentUser) {
- settings = mSettings;
- } else {
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo curIm = null;
String curInputMethodId = settings.getSelectedInputMethod();
@@ -1118,16 +1125,17 @@
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
}
+ if (isCurrentUser
+ && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
+ return;
+ }
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (!isCurrentUser) {
return;
}
-
- if (!(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) {
- return;
- }
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- newAdditionalSubtypeMap, DirectBootAwareness.AUTO);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
boolean changed = false;
@@ -1281,21 +1289,20 @@
void onUnlockUser(@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final int currentUserId = mSettings.getUserId();
if (DEBUG) {
- Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
- }
- if (userId != currentUserId) {
- return;
+ Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + mCurrentUserId);
}
if (!mSystemReady) {
return;
}
- mSettings = queryInputMethodServicesInternal(mContext, userId,
- AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
- // We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
+ if (mCurrentUserId == userId) {
+ // We need to rebuild IMEs.
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ }
}
}
@@ -1361,19 +1368,20 @@
mShowOngoingImeSwitcherForPhones = false;
- AdditionalSubtypeMapRepository.initialize(mHandler);
+ // InputMethodSettingsRepository should be initialized before buildInputMethodListLocked
+ InputMethodSettingsRepository.initialize(mHandler, mContext);
+ AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
- final int userId = mActivityManagerInternal.getCurrentUserId();
+ mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
- // mSettings should be created before buildInputMethodListLocked
- mSettings = InputMethodSettings.createEmptyMap(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
mSwitchingController =
InputMethodSubtypeSwitchingController.createInstanceLocked(context,
- mSettings.getMethodMap(), userId);
+ settings.getMethodMap(), settings.getUserId());
mHardwareKeyboardShortcutController =
- new HardwareKeyboardShortcutController(mSettings.getMethodMap(),
- mSettings.getUserId());
+ new HardwareKeyboardShortcutController(settings.getMethodMap(),
+ settings.getUserId());
mMenuController = new InputMethodMenuController(this);
mBindingController =
bindingControllerForTesting != null
@@ -1405,7 +1413,7 @@
@GuardedBy("ImfLock.class")
@UserIdInt
int getCurrentImeUserIdLocked() {
- return mSettings.getUserId();
+ return mCurrentUserId;
}
private final class InkWindowInitializer implements Runnable {
@@ -1441,12 +1449,13 @@
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
String selectedMethodId = getSelectedMethodIdLocked();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (selectedMethodId != null
- && !mSettings.getMethodMap().get(selectedMethodId).isSystem()) {
+ && !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
}
final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
- context, mSettings.getEnabledInputMethodList());
+ context, settings.getEnabledInputMethodList());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
@@ -1502,7 +1511,7 @@
IInputMethodClientInvoker clientToBeReset) {
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mSettings.getUserId());
+ + " currentUserId=" + mCurrentUserId);
}
maybeInitImeNavbarConfigLocked(newUserId);
@@ -1510,8 +1519,9 @@
// ContentObserver should be registered again when the user is changed
mSettingsObserver.registerContentObserverLocked(newUserId);
- mSettings = InputMethodSettings.createEmptyMap(newUserId);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ mCurrentUserId = newUserId;
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
if (DEBUG) {
Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
@@ -1529,10 +1539,9 @@
// and user switch would not happen at that time.
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_USER);
- mSettings = queryInputMethodServicesInternal(mContext, newUserId,
- AdditionalSubtypeMapRepository.get(newUserId), DirectBootAwareness.AUTO);
+ final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
- if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
+ if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
resetDefaultImeLocked(mContext);
@@ -1542,12 +1551,12 @@
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, newUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
if (DEBUG) {
Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
- + " selectedIme=" + mSettings.getSelectedInputMethod());
+ + " selectedIme=" + newSettings.getSelectedInputMethod());
}
if (mIsInteractive && clientToBeReset != null) {
@@ -1570,7 +1579,7 @@
}
if (!mSystemReady) {
mSystemReady = true;
- final int currentUserId = mSettings.getUserId();
+ final int currentUserId = mCurrentUserId;
mStatusBarManagerInternal =
LocalServices.getService(StatusBarManagerInternal.class);
hideStatusBarIconLocked();
@@ -1591,7 +1600,7 @@
// the "mImeDrawsImeNavBarResLazyInitFuture" field.
synchronized (ImfLock.class) {
mImeDrawsImeNavBarResLazyInitFuture = null;
- if (currentUserId != mSettings.getUserId()) {
+ if (currentUserId != mCurrentUserId) {
// This means that the current user is already switched to other user
// before the background task is executed. In this scenario the relevant
// field should already be initialized.
@@ -1610,17 +1619,19 @@
UserHandle.ALL, broadcastFilterForAllUsers, null, null,
Context.RECEIVER_EXPORTED);
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = SecureSettingsWrapper.getString(
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, currentUserId);
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
- mSettings = queryInputMethodServicesInternal(mContext, currentUserId,
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ currentUserId, AdditionalSubtypeMapRepository.get(currentUserId),
DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(currentUserId, newSettings);
postInputMethodSettingUpdatedLocked(
!imeSelectedOnBoot /* resetDefaultEnabledIme */);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
- mSettings.getEnabledInputMethodList());
+ newSettings.getEnabledInputMethodList());
}
}
}
@@ -1667,7 +1678,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1690,7 +1701,7 @@
}
synchronized (ImfLock.class) {
final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId,
- mSettings.getUserId(), null);
+ mCurrentUserId, null);
if (resolvedUserIds.length != 1) {
return Collections.emptyList();
}
@@ -1718,14 +1729,12 @@
}
// Check if selected IME of current user supports handwriting.
- if (userId == mSettings.getUserId()) {
+ if (userId == mCurrentUserId) {
return mBindingController.supportsStylusHandwriting()
&& (!connectionless
|| mBindingController.supportsConnectionlessStylusHandwriting());
}
- //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
- //TODO(b/210039666): use cache.
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(
settings.getSelectedInputMethod());
return imi != null && imi.supportsStylusHandwriting()
@@ -1749,9 +1758,8 @@
private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
- if (userId == mSettings.getUserId()
- && directBootAwareness == DirectBootAwareness.AUTO) {
- settings = mSettings;
+ if (directBootAwareness == DirectBootAwareness.AUTO) {
+ settings = InputMethodSettingsRepository.get(userId);
} else {
final AdditionalSubtypeMap additionalSubtypeMap =
AdditionalSubtypeMapRepository.get(userId);
@@ -1769,15 +1777,8 @@
@GuardedBy("ImfLock.class")
private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
int callingUid) {
- final ArrayList<InputMethodInfo> methodList;
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- methodList = mSettings.getEnabledInputMethodList();
- settings = mSettings;
- } else {
- settings = queryMethodMapForUserLocked(userId);
- methodList = settings.getEnabledInputMethodList();
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
// filter caller's access to input methods
methodList.removeIf(imi ->
!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings));
@@ -1835,22 +1836,7 @@
@GuardedBy("ImfLock.class")
private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
- if (userId == mSettings.getUserId()) {
- final InputMethodInfo imi;
- String selectedMethodId = getSelectedMethodIdLocked();
- if (imiId == null && selectedMethodId != null) {
- imi = mSettings.getMethodMap().get(selectedMethodId);
- } else {
- imi = mSettings.getMethodMap().get(imiId);
- }
- if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
- return Collections.emptyList();
- }
- return mSettings.getEnabledInputMethodSubtypeList(
- imi, allowsImplicitlyEnabledSubtypes);
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
if (imi == null) {
return Collections.emptyList();
@@ -2031,7 +2017,7 @@
final boolean restarting = !initial;
final Binder startInputToken = new Binder();
- final StartInputInfo info = new StartInputInfo(mSettings.getUserId(),
+ final StartInputInfo info = new StartInputInfo(mCurrentUserId,
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
@@ -2045,9 +2031,9 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (mSettings.getUserId() == UserHandle.getUserId(
+ if (mCurrentUserId == UserHandle.getUserId(
mCurClient.mUid)) {
- mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(),
+ mPackageManagerInternal.grantImplicitAccess(mCurrentUserId,
null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()),
mCurClient.mUid, true /* direct */);
}
@@ -2070,7 +2056,8 @@
}
String curId = getCurIdLocked();
- final InputMethodInfo curInputMethodInfo = mSettings.getMethodMap().get(curId);
+ final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(curId);
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
@@ -2258,17 +2245,18 @@
return currentMethodId;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final int oldDeviceId = mDeviceIdToShowIme;
mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
if (oldDeviceId == DEVICE_ID_DEFAULT) {
return currentMethodId;
}
- final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+ final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod();
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(null);
+ settings.putSelectedDefaultDeviceInputMethod(null);
return defaultDeviceMethodId;
}
@@ -2276,7 +2264,7 @@
mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
if (Objects.equals(deviceMethodId, currentMethodId)) {
return currentMethodId;
- } else if (!mSettings.getMethodMap().containsKey(deviceMethodId)) {
+ } else if (!settings.getMethodMap().containsKey(deviceMethodId)) {
if (DEBUG) {
Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
+ " because its custom input method is not available: " + deviceMethodId);
@@ -2288,7 +2276,7 @@
if (DEBUG) {
Slog.v(TAG, "Storing default device input method " + currentMethodId);
}
- mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+ settings.putSelectedDefaultDeviceInputMethod(currentMethodId);
}
if (DEBUG) {
Slog.v(TAG, "Switching current input method from " + currentMethodId
@@ -2318,7 +2306,8 @@
if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
return false;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(selectedMethodId);
if (imi == null) {
return false;
}
@@ -2662,7 +2651,7 @@
} else if (packageName != null) {
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
final PackageManager userAwarePackageManager =
- getPackageManagerForUser(mContext, mSettings.getUserId());
+ getPackageManagerForUser(mContext, mCurrentUserId);
ApplicationInfo applicationInfo = null;
try {
applicationInfo = userAwarePackageManager.getApplicationInfo(packageName,
@@ -2724,7 +2713,7 @@
return false;
}
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) {
+ && mWindowManagerInternal.isKeyguardSecure(mCurrentUserId)) {
return false;
}
if ((visibility & InputMethodService.IME_ACTIVE) == 0
@@ -2741,7 +2730,8 @@
return false;
}
- List<InputMethodInfo> imes = mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter(
InputMethodInfo::shouldShowInInputMethodPicker);
final int numImes = imes.size();
if (numImes > 2) return true;
@@ -2753,7 +2743,7 @@
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = imes.get(i);
final List<InputMethodSubtype> subtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
@@ -2905,11 +2895,12 @@
@GuardedBy("ImfLock.class")
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabledMayChange) {
final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
- mSettings.getUserId());
+ settings.getUserId());
- List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
for (int i = 0; i < enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
@@ -2936,20 +2927,20 @@
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_INPUT_METHOD, null, settings.getUserId());
String defaultDeviceIme = SecureSettingsWrapper.getString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
if (DEBUG) {
Slog.v(TAG, "Current input method " + ime + " differs from the stored default"
- + " device input method for user " + mSettings.getUserId()
+ + " device input method for user " + settings.getUserId()
+ " - restoring " + defaultDeviceIme);
}
SecureSettingsWrapper.putString(
Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme,
- mSettings.getUserId());
+ settings.getUserId());
SecureSettingsWrapper.putString(
- Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, mSettings.getUserId());
+ Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, settings.getUserId());
}
}
@@ -2957,14 +2948,14 @@
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
- String id = mSettings.getSelectedInputMethod();
+ String id = settings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
- id = mSettings.getSelectedInputMethod();
+ id = settings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+ setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
@@ -2975,18 +2966,18 @@
}
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
}
@@ -3010,14 +3001,15 @@
@GuardedBy("ImfLock.class")
void setInputMethodLocked(String id, int subtypeId, int deviceId) {
- InputMethodInfo info = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ InputMethodInfo info = settings.getMethodMap().get(id);
if (info == null) {
throw getExceptionForUnknownImeId(id);
}
// See if we need to notify a subtype change within the same IME.
if (id.equals(getSelectedMethodIdLocked())) {
- final int userId = mSettings.getUserId();
+ final int userId = settings.getUserId();
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
notifyInputMethodSubtypeChangedLocked(userId, info, null);
@@ -3058,7 +3050,7 @@
// method is a custom one specific to a virtual device. So only update the settings
// entry used to restore the default device input method once we want to show the IME
// back on the default device.
- mSettings.putSelectedDefaultDeviceInputMethod(id);
+ settings.putSelectedDefaultDeviceInputMethod(id);
return;
}
IInputMethodInvoker curMethod = getCurMethodLocked();
@@ -3586,7 +3578,7 @@
return InputBindResult.USER_SWITCHING;
}
final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds(
- mSettings.getUserId(), false /* enabledOnly */);
+ mCurrentUserId, false /* enabledOnly */);
for (int profileId : profileIdsWithDisabled) {
if (profileId == userId) {
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3632,10 +3624,10 @@
}
// Verify if caller is a background user.
- final int currentUserId = mSettings.getUserId();
- if (userId != currentUserId) {
+ if (userId != mCurrentUserId) {
if (ArrayUtils.contains(
- mUserManagerInternal.getProfileIds(currentUserId, false), userId)) {
+ mUserManagerInternal.getProfileIds(mCurrentUserId, false),
+ userId)) {
// cross-profile access is always allowed here to allow
// profile-switching.
scheduleSwitchUserTaskLocked(userId, cs.mClient);
@@ -3824,7 +3816,7 @@
&& mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
- if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
+ if (mCurrentUserId != UserHandle.getUserId(uid)) {
return false;
}
if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid(
@@ -3888,9 +3880,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
@@ -3906,9 +3899,10 @@
if (!calledWithValidTokenLocked(token)) {
return;
}
- final InputMethodInfo imi = mSettings.getMethodMap().get(id);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodInfo imi = settings.getMethodMap().get(id);
if (imi == null || !canCallerAccessInputMethod(
- imi.getPackageName(), callingUid, userId, mSettings)) {
+ imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
if (subtype != null) {
@@ -3926,10 +3920,11 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtype();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
final InputMethodInfo lastImi;
if (lastIme != null) {
- lastImi = mSettings.getMethodMap().get(lastIme.first);
+ lastImi = settings.getMethodMap().get(lastIme.first);
} else {
lastImi = null;
}
@@ -3953,7 +3948,7 @@
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
- final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList();
if (enabled != null) {
final int enabledCount = enabled.size();
final String locale;
@@ -3961,7 +3956,7 @@
&& !TextUtils.isEmpty(mCurrentSubtype.getLocale())) {
locale = mCurrentSubtype.getLocale();
} else {
- locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString();
+ locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
}
for (int i = 0; i < enabledCount; ++i) {
final InputMethodInfo imi = enabled.get(i);
@@ -4008,8 +4003,9 @@
@GuardedBy("ImfLock.class")
private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
- onlyCurrentIme, mSettings.getMethodMap().get(getSelectedMethodIdLocked()),
+ onlyCurrentIme, settings.getMethodMap().get(getSelectedMethodIdLocked()),
mCurrentSubtype);
if (nextSubtype == null) {
return false;
@@ -4025,9 +4021,10 @@
if (!calledWithValidTokenLocked(token)) {
return false;
}
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
false /* onlyCurrentIme */,
- mSettings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
+ settings.getMethodMap().get(getSelectedMethodIdLocked()), mCurrentSubtype);
return nextSubtype != null;
}
}
@@ -4039,12 +4036,7 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
- return mSettings.getLastInputMethodSubtype();
- }
-
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getLastInputMethodSubtype();
+ return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype();
}
}
@@ -4075,23 +4067,20 @@
}
final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
- final boolean isCurrentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = isCurrentUser
- ? mSettings
- : queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
+ final boolean isCurrentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap(
imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid);
if (additionalSubtypeMap != newAdditionalSubtypeMap) {
AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap,
settings.getMethodMap());
+ final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext,
+ userId, AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
final long ident = Binder.clearCallingIdentity();
try {
- mSettings = queryInputMethodServicesInternal(mContext,
- mSettings.getUserId(),
- AdditionalSubtypeMapRepository.get(mSettings.getUserId()),
- DirectBootAwareness.AUTO);
postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4121,9 +4110,8 @@
final long ident = Binder.clearCallingIdentity();
try {
synchronized (ImfLock.class) {
- final boolean currentUser = (mSettings.getUserId() == userId);
- final InputMethodSettings settings = currentUser
- ? mSettings : queryMethodMapForUserLocked(userId);
+ final boolean currentUser = (mCurrentUserId == userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
return;
}
@@ -4465,11 +4453,11 @@
}
return;
}
- if (mSettings.getUserId() != mSwitchingController.getUserId()) {
+ if (mCurrentUserId != mSwitchingController.getUserId()) {
return;
}
- final InputMethodInfo imi =
- mSettings.getMethodMap().get(getSelectedMethodIdLocked());
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ .getMethodMap().get(getSelectedMethodIdLocked());
if (imi != null) {
mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
}
@@ -4529,8 +4517,9 @@
return;
} else {
// Called with current IME's token.
- if (mSettings.getMethodMap().get(id) != null
- && mSettings.getEnabledInputMethodListWithFilter(
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ if (settings.getMethodMap().get(id) != null
+ && settings.getEnabledInputMethodListWithFilter(
(info) -> info.getId().equals(id)).isEmpty()) {
throw new IllegalStateException("Requested IME is not enabled: " + id);
}
@@ -4709,21 +4698,23 @@
return false;
}
synchronized (ImfLock.class) {
+ final InputMethodSettings settings =
+ InputMethodSettingsRepository.get(mCurrentUserId);
final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
- && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId());
- final String lastInputMethodId = mSettings.getSelectedInputMethod();
+ && mWindowManagerInternal.isKeyguardSecure(settings.getUserId());
+ final String lastInputMethodId = settings.getSelectedInputMethod();
int lastInputMethodSubtypeId =
- mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+ settings.getSelectedInputMethodSubtypeId(lastInputMethodId);
final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
.getSortedInputMethodAndSubtypeList(
showAuxSubtypes, isScreenLocked, true /* forImeMenu */,
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), settings.getUserId());
if (imList.isEmpty()) {
Slog.w(TAG, "Show switching menu failed, imList is empty,"
+ " showAuxSubtypes: " + showAuxSubtypes
+ " isScreenLocked: " + isScreenLocked
- + " userId: " + mSettings.getUserId());
+ + " userId: " + settings.getUserId());
return false;
}
@@ -4909,8 +4900,9 @@
@GuardedBy("ImfLock.class")
private boolean chooseNewDefaultIMELocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
+ settings.getEnabledInputMethodList());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
@@ -5024,6 +5016,8 @@
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
@@ -5034,7 +5028,7 @@
final List<ResolveInfo> allInputMethodServices =
mContext.getPackageManager().queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
- PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId());
+ PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId());
final int numImes = allInputMethodServices.size();
for (int i = 0; i < numImes; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
@@ -5049,11 +5043,11 @@
if (!resetDefaultEnabledIme) {
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
- final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodList();
+ final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList();
final int numImes = enabledImes.size();
for (int i = 0; i < numImes; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
- if (mSettings.getMethodMap().containsKey(imi.getId())) {
+ if (settings.getMethodMap().containsKey(imi.getId())) {
enabledImeFound = true;
if (!imi.isAuxiliaryIme()) {
enabledNonAuxImeFound = true;
@@ -5077,7 +5071,7 @@
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
- InputMethodInfoUtils.getDefaultEnabledImes(mContext, mSettings.getMethodList(),
+ InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(),
reenableMinimumNonAuxSystemImes);
final int numImes = defaultEnabledIme.size();
for (int i = 0; i < numImes; ++i) {
@@ -5089,9 +5083,9 @@
}
}
- final String defaultImiId = mSettings.getSelectedInputMethod();
+ final String defaultImiId = settings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
- if (!mSettings.getMethodMap().containsKey(defaultImiId)) {
+ if (!settings.getMethodMap().containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);
@@ -5105,26 +5099,26 @@
updateDefaultVoiceImeIfNeededLocked();
// TODO: Instantiate mSwitchingController for each user.
- if (mSettings.getUserId() == mSwitchingController.getUserId()) {
- mSwitchingController.resetCircularListLocked(mSettings.getMethodMap());
+ if (settings.getUserId() == mSwitchingController.getUserId()) {
+ mSwitchingController.resetCircularListLocked(settings.getMethodMap());
} else {
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
- mContext, mSettings.getMethodMap(), mSettings.getUserId());
+ mContext, settings.getMethodMap(), mCurrentUserId);
}
// TODO: Instantiate mHardwareKeyboardShortcutController for each user.
- if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
- mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap());
+ if (settings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+ mHardwareKeyboardShortcutController.reset(settings.getMethodMap());
} else {
mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
- mSettings.getMethodMap(), mSettings.getUserId());
+ settings.getMethodMap(), settings.getUserId());
}
sendOnNavButtonFlagsChangedLocked();
// Notify InputMethodListListeners of the new installed InputMethods.
- final List<InputMethodInfo> inputMethodList = mSettings.getMethodList();
+ final List<InputMethodInfo> inputMethodList = settings.getMethodList();
mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED,
- mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
+ settings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
@GuardedBy("ImfLock.class")
@@ -5139,11 +5133,12 @@
@GuardedBy("ImfLock.class")
private void updateDefaultVoiceImeIfNeededLocked() {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
- final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+ final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod();
final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
- mSettings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
+ settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId);
if (newSystemVoiceIme == null) {
if (DEBUG) {
Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
@@ -5152,7 +5147,7 @@
// Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
// does not update the actual Secure Settings until the user is unlocked.
if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
- mSettings.putDefaultVoiceInputMethod("");
+ settings.putDefaultVoiceInputMethod("");
// We don't support disabling the voice ime when a package is removed from the
// config.
}
@@ -5165,7 +5160,7 @@
Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
}
setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
- mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+ settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
}
// ----------------------------------------------------------------------
@@ -5180,8 +5175,9 @@
*/
@GuardedBy("ImfLock.class")
private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
if (enabled) {
- final String enabledImeIdsStr = mSettings.getEnabledInputMethodsStr();
+ final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
enabledImeIdsStr, id);
if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) {
@@ -5189,29 +5185,29 @@
// Nothing to do. The previous state was enabled.
return true;
}
- mSettings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
+ settings.putEnabledInputMethodsStr(newEnabledImeIdsStr);
// Previous state was disabled.
return false;
} else {
- final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+ final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings
.getEnabledInputMethodsAndSubtypeList();
StringBuilder builder = new StringBuilder();
- if (mSettings.buildAndPutEnabledInputMethodsStrRemovingId(
+ if (settings.buildAndPutEnabledInputMethodsStrRemovingId(
builder, enabledInputMethodsList, id)) {
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
// Disabled input method is currently selected, switch to another one.
- final String selId = mSettings.getSelectedInputMethod();
+ final String selId = settings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
}
- } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+ } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) {
// Disabled default device IME while using a virtual device one, choose a
// new default one but only update the settings.
InputMethodInfo newDefaultIme =
InputMethodInfoUtils.getMostApplicableDefaultIME(
- mSettings.getEnabledInputMethodList());
- mSettings.putSelectedDefaultDeviceInputMethod(
+ settings.getEnabledInputMethodList());
+ settings.putSelectedDefaultDeviceInputMethod(
newDefaultIme == null ? null : newDefaultIme.getId());
}
// Previous state was enabled.
@@ -5227,29 +5223,30 @@
@GuardedBy("ImfLock.class")
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
mCurrentSubtype = null;
} else {
if (subtypeId < imi.getSubtypeCount()) {
InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
- mSettings.putSelectedSubtype(subtype.hashCode());
+ settings.putSelectedSubtype(subtype.hashCode());
mCurrentSubtype = subtype;
} else {
- mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
- notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype);
+ notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, mCurrentSubtype);
if (!setSubtypeOnly) {
// Set InputMethod here
- mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+ settings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
@@ -5257,13 +5254,15 @@
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
mDisplayIdToShowIme = INVALID_DISPLAY;
- mSettings.putSelectedDefaultDeviceInputMethod(null);
- InputMethodInfo imi = mSettings.getMethodMap().get(newDefaultIme);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ settings.putSelectedDefaultDeviceInputMethod(null);
+
+ InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
- String subtypeHashCode = mSettings.getLastSubtypeForInputMethod(newDefaultIme);
+ String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme);
if (subtypeHashCode != null) {
try {
lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
@@ -5290,12 +5289,12 @@
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
synchronized (ImfLock.class) {
- if (mSettings.getUserId() == userId) {
+ if (mCurrentUserId == userId) {
return getCurrentInputMethodSubtypeLocked();
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
- return settings.getCurrentInputMethodSubtypeForNonCurrentUsers();
+ return InputMethodSettingsRepository.get(userId)
+ .getCurrentInputMethodSubtypeForNonCurrentUsers();
}
}
@@ -5315,26 +5314,27 @@
if (selectedMethodId == null) {
return null;
}
- final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
- final InputMethodInfo imi = mSettings.getMethodMap().get(selectedMethodId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final boolean subtypeIsSelected = settings.isSubtypeSelected();
+ final InputMethodInfo imi = settings.getMethodMap().get(selectedMethodId);
if (imi == null || imi.getSubtypeCount() == 0) {
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
|| !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
- int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
+ int subtypeId = settings.getSelectedInputMethodSubtypeId(selectedMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- mSettings.getEnabledInputMethodSubtypeList(imi, true);
+ settings.getEnabledInputMethodSubtypeList(imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- final String locale = SystemLocaleWrapper.get(mSettings.getUserId())
+ final String locale = SystemLocaleWrapper.get(settings.getUserId())
.get(0).toString();
mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype(
explicitlyOrImplicitlyEnabledSubtypes,
@@ -5357,38 +5357,22 @@
*/
@GuardedBy("ImfLock.class")
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings;
- if (userId == mSettings.getUserId()) {
- settings = mSettings;
- } else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- settings = queryInputMethodServicesInternal(mContext, userId,
- additionalSubtypeMap, DirectBootAwareness.AUTO);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@GuardedBy("ImfLock.class")
- private InputMethodSettings queryMethodMapForUserLocked(@UserIdInt int userId) {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- return queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- DirectBootAwareness.AUTO);
- }
-
- @GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)
- || !mSettings.getEnabledInputMethodList()
- .contains(mSettings.getMethodMap().get(imeId))) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (!settings.getMethodMap().containsKey(imeId)
+ || !settings.getEnabledInputMethodList()
+ .contains(settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
return true;
}
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (!settings.getMethodMap().containsKey(imeId)
|| !settings.getEnabledInputMethodList().contains(
settings.getMethodMap().get(imeId))) {
@@ -5429,8 +5413,9 @@
@GuardedBy("ImfLock.class")
private void switchKeyboardLayoutLocked(int direction) {
- final InputMethodInfo currentImi = mSettings.getMethodMap().get(
- getSelectedMethodIdLocked());
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+
+ final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
if (currentImi == null) {
return;
}
@@ -5442,7 +5427,7 @@
if (nextSubtypeHandle == null) {
return;
}
- final InputMethodInfo nextImi = mSettings.getMethodMap().get(nextSubtypeHandle.getImeId());
+ final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId());
if (nextImi == null) {
return;
}
@@ -5521,17 +5506,14 @@
@Override
public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- if (userId == mSettings.getUserId()) {
- if (!mSettings.getMethodMap().containsKey(imeId)) {
- return false; // IME is not found.
- }
- setInputMethodEnabledLocked(imeId, enabled);
- return true;
- }
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (!settings.getMethodMap().containsKey(imeId)) {
return false; // IME is not found.
}
+ if (userId == mCurrentUserId) {
+ setInputMethodEnabledLocked(imeId, enabled);
+ return true;
+ }
if (enabled) {
final String enabledImeIdsStr = settings.getEnabledInputMethodsStr();
final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(
@@ -5858,8 +5840,9 @@
final Printer p = new PrintWriterPrinter(pw);
synchronized (ImfLock.class) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
p.println("Current Input Method Manager state:");
- final List<InputMethodInfo> methodList = mSettings.getMethodList();
+ final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
for (int i = 0; i < numImes; i++) {
@@ -5883,6 +5866,7 @@
p.println(" curSession=" + c.mCurSession);
};
mClientController.forAllClients(clientControllerDump);
+ p.println(" mCurrentUserId=" + mCurrentUserId);
p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
@@ -5908,8 +5892,6 @@
? Arrays.toString(mStylusIds.toArray()) : ""));
p.println(" mSwitchingController:");
mSwitchingController.dump(p);
- p.println(" mSettings:");
- mSettings.dump(p, " ");
p.println(" mStartInputHistory:");
mStartInputHistory.dump(pw, " ");
@@ -6164,7 +6146,7 @@
}
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
for (int userId : userIds) {
final List<InputMethodInfo> methods = all
@@ -6209,7 +6191,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6268,14 +6250,14 @@
PrintWriter error) {
boolean failedToEnableUnknownIme = false;
boolean previouslyEnabled = false;
- if (userId == mSettings.getUserId()) {
- if (enabled && !mSettings.getMethodMap().containsKey(imeId)) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
+ if (enabled && !settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
} else {
previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
}
} else {
- final InputMethodSettings settings = queryMethodMapForUserLocked(userId);
if (enabled) {
if (!settings.getMethodMap().containsKey(imeId)) {
failedToEnableUnknownIme = true;
@@ -6330,7 +6312,7 @@
PrintWriter error = shellCommand.getErrPrintWriter()) {
synchronized (ImfLock.class) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6370,7 +6352,7 @@
synchronized (ImfLock.class) {
try (PrintWriter out = shellCommand.getOutPrintWriter()) {
final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mSettings.getUserId(), shellCommand.getErrPrintWriter());
+ mCurrentUserId, shellCommand.getErrPrintWriter());
for (int userId : userIds) {
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
@@ -6382,15 +6364,16 @@
}
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
- if (userId == mSettings.getUserId()) {
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ if (userId == mCurrentUserId) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
// Enable default IMEs, disable others
- var toDisable = mSettings.getEnabledInputMethodList();
+ var toDisable = settings.getEnabledInputMethodList();
var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes(
- mContext, mSettings.getMethodList());
+ mContext, settings.getMethodList());
toDisable.removeAll(defaultEnabled);
for (InputMethodInfo info : toDisable) {
setInputMethodEnabledLocked(info.getId(), false);
@@ -6404,16 +6387,11 @@
}
updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
- getPackageManagerForUser(mContext, mSettings.getUserId()),
- mSettings.getEnabledInputMethodList());
- nextIme = mSettings.getSelectedInputMethod();
- nextEnabledImes = mSettings.getEnabledInputMethodList();
+ getPackageManagerForUser(mContext, settings.getUserId()),
+ settings.getEnabledInputMethodList());
+ nextIme = settings.getSelectedInputMethod();
+ nextEnabledImes = settings.getEnabledInputMethodList();
} else {
- final AdditionalSubtypeMap additionalSubtypeMap =
- AdditionalSubtypeMapRepository.get(userId);
- final InputMethodSettings settings = queryInputMethodServicesInternal(
- mContext, userId, additionalSubtypeMap, DirectBootAwareness.AUTO);
-
nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
settings.getMethodList());
nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
new file mode 100644
index 0000000..60b9a4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettingsRepository.java
@@ -0,0 +1,86 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+final class InputMethodSettingsRepository {
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ private static final SparseArray<InputMethodSettings> sPerUserMap = new SparseArray<>();
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private InputMethodSettingsRepository() {
+ }
+
+ @NonNull
+ @GuardedBy("ImfLock.class")
+ static InputMethodSettings get(@UserIdInt int userId) {
+ final InputMethodSettings obj = sPerUserMap.get(userId);
+ if (obj != null) {
+ return obj;
+ }
+ return InputMethodSettings.createEmptyMap(userId);
+ }
+
+ @GuardedBy("ImfLock.class")
+ static void put(@UserIdInt int userId, @NonNull InputMethodSettings obj) {
+ sPerUserMap.put(userId, obj);
+ }
+
+ static void initialize(@NonNull Handler handler, @NonNull Context context) {
+ final UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
+ handler.post(() -> {
+ userManagerInternal.addUserLifecycleListener(
+ new UserManagerInternal.UserLifecycleListener() {
+ @Override
+ public void onUserRemoved(UserInfo user) {
+ final int userId = user.id;
+ handler.post(() -> {
+ synchronized (ImfLock.class) {
+ sPerUserMap.remove(userId);
+ }
+ });
+ }
+ });
+ synchronized (ImfLock.class) {
+ for (int userId : userManagerInternal.getUserIds()) {
+ final InputMethodSettings settings =
+ InputMethodManagerService.queryInputMethodServicesInternal(
+ context,
+ userId,
+ AdditionalSubtypeMapRepository.get(userId),
+ DirectBootAwareness.AUTO);
+ sPerUserMap.put(userId, settings);
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 194ab04..3d68555 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1458,14 +1458,10 @@
@Override
public IBinder getBinderForSetQueue() throws RemoteException {
return new ParcelableListBinder<QueueItem>(
+ QueueItem.class,
(list) -> {
- // Checking list items are instanceof QueueItem to validate against
- // malicious apps calling it directly via reflection with non compilable
- // items. See b/317048338 for more details
- List<QueueItem> sanitizedQueue =
- list.stream().filter(it -> it instanceof QueueItem).toList();
synchronized (mLock) {
- mQueue = sanitizedQueue;
+ mQueue = list;
}
mHandler.post(MessageHandler.MSG_UPDATE_QUEUE);
});
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index bd73cb6..1938642 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -23,9 +23,15 @@
import android.app.Notification;
import android.app.NotificationChannel;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.LoggingOnly;
import android.content.Context;
import android.media.AudioAttributes;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Slog;
+import com.android.internal.compat.IPlatformCompat;
/**
* Stores the latest notification channel information for this notification
@@ -34,14 +40,26 @@
private static final String TAG = "ChannelExtractor";
private static final boolean DBG = false;
+ /**
+ * Corrects audio attributes for notifications based on characteristics of the notifications.
+ */
+ @ChangeId
+ @LoggingOnly
+ static final long RESTRICT_AUDIO_ATTRIBUTES = 331793339L;
+
private RankingConfig mConfig;
private Context mContext;
+ private IPlatformCompat mPlatformCompat;
public void initialize(Context ctx, NotificationUsageStats usageStats) {
mContext = ctx;
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
}
+ public void setCompatChangeLogger(IPlatformCompat platformCompat) {
+ mPlatformCompat = platformCompat;
+ }
+
public RankingReconsideration process(NotificationRecord record) {
if (record == null || record.getNotification() == null) {
if (DBG) Slog.d(TAG, "skipping empty notification");
@@ -80,6 +98,7 @@
}
if (updateAttributes) {
+ reportAudioAttributesChanged(record.getUid());
NotificationChannel clone = record.getChannel().copy();
clone.setSound(clone.getSound(), new AudioAttributes.Builder(attributes)
.setUsage(USAGE_NOTIFICATION)
@@ -91,6 +110,17 @@
return null;
}
+ private void reportAudioAttributesChanged(int uid) {
+ final long id = Binder.clearCallingIdentity();
+ try {
+ mPlatformCompat.reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, uid);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unexpected exception while reporting to changecompat", e);
+ } finally {
+ Binder.restoreCallingIdentity(id);
+ }
+ }
+
@Override
public void setConfig(RankingConfig config) {
mConfig = config;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
index 2cc63eb..a3a91e2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerPrivate.java
@@ -25,4 +25,6 @@
interface NotificationManagerPrivate {
@Nullable
NotificationRecord getNotificationByKey(String key);
+
+ void timeoutNotification(String key);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b14242e..b48cad2 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -139,7 +139,6 @@
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
-
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -221,6 +220,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -238,6 +238,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.DeviceIdleManager;
import android.os.Environment;
import android.os.Handler;
@@ -305,7 +306,6 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
import android.widget.Toast;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -357,9 +357,7 @@
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.BackgroundActivityStartCallback;
import com.android.server.wm.WindowManagerInternal;
-
import libcore.io.IoUtils;
-
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
@@ -521,7 +519,7 @@
/**
* Apps that post custom toasts in the background will have those blocked. Apps can
* still post toasts created with
- * {@link android.widget.Toast#makeText(Context, CharSequence, int)} and its variants while
+ * {@link Toast#makeText(Context, CharSequence, int)} and its variants while
* in the background.
*/
@ChangeId
@@ -556,7 +554,7 @@
/**
* Rate limit showing toasts, on a per package basis.
*
- * It limits the number of {@link android.widget.Toast#show()} calls to prevent overburdening
+ * It limits the number of {@link Toast#show()} calls to prevent overburdening
* the user with too many toasts in a limited time. Any attempt to show more toasts than allowed
* in a certain time frame will result in the toast being discarded.
*/
@@ -580,9 +578,9 @@
static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
/**
- * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and
- * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the
- * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
+ * App calls to {@link NotificationManager#setInterruptionFilter} and
+ * {@link NotificationManager#setNotificationPolicy} manage DND through the
+ * creation and activation of an implicit {@link AutomaticZenRule}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
@@ -624,6 +622,8 @@
private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
+ private LockPatternUtils mLockUtils;
+
final IBinder mForegroundToken = new Binder();
@VisibleForTesting
WorkerHandler mHandler;
@@ -676,6 +676,10 @@
private static final int DB_VERSION = 1;
+
+ private static final String ADSERVICES_MODULE_PKG_NAME =
+ "com.android.adservices";
+
private static final String TAG_NOTIFICATION_POLICY = "notification-policy";
private static final String ATTR_VERSION = "version";
@@ -709,6 +713,7 @@
private NotificationHistoryManager mHistoryManager;
protected SnoozeHelper mSnoozeHelper;
+ private TimeToLiveHelper mTtlHelper;
private GroupHelper mGroupHelper;
private int mAutoGroupAtCount;
private boolean mIsTelevision;
@@ -734,6 +739,10 @@
// Broadcast intent receiver for notification permissions review-related intents
private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver;
+ private AppOpsManager.OnOpChangedListener mAppOpsListener;
+
+ private ModuleInfo mAdservicesModuleInfo;
+
static class Archive {
final SparseArray<Boolean> mEnabled;
final int mBufferSize;
@@ -779,7 +788,7 @@
public StatusBarNotification[] getArray(UserManager um, int count, boolean includeSnoozed) {
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : um.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -902,14 +911,14 @@
@VisibleForTesting
boolean isDNDMigrationDone(int userId) {
- return Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+ return Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
}
@VisibleForTesting
void setDNDMigrationDone(int userId) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.DND_CONFIGS_MIGRATED, 1, userId);
}
protected void migrateDefaultNAS() {
@@ -936,15 +945,15 @@
@VisibleForTesting
void setNASMigrationDone(int baseUserId) {
for (int profileId : mUm.getProfileIds(baseUserId, false)) {
- Settings.Secure.putIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 1, profileId);
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 1, profileId);
}
}
@VisibleForTesting
boolean isNASMigrationDone(int userId) {
- return (Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
+ return (Secure.getIntForUser(getContext().getContentResolver(),
+ Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1);
}
boolean isProfileUser(UserInfo userInfo) {
@@ -1097,7 +1106,7 @@
mSnoozeHelper.readXml(parser, System.currentTimeMillis());
}
if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
- if (forRestore && userId != UserHandle.USER_SYSTEM) {
+ if (forRestore && userId != USER_SYSTEM) {
continue;
}
mLockScreenAllowSecureNotifications = parser.getAttributeBoolean(null,
@@ -1141,7 +1150,7 @@
InputStream infile = null;
try {
infile = mPolicyFile.openRead();
- readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+ readPolicyXml(infile, false /*forRestore*/, USER_ALL);
// We re-load the default dnd packages to allow the newly added and denined.
final boolean isWatch = mPackageManagerClient.hasSystemFeature(
@@ -1191,7 +1200,7 @@
}
try {
- writePolicyXml(stream, false /*forBackup*/, UserHandle.USER_ALL);
+ writePolicyXml(stream, false /*forBackup*/, USER_ALL);
mPolicyFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file, restoring backup", e);
@@ -1220,7 +1229,7 @@
mAssistants.writeXml(out, forBackup, userId);
mSnoozeHelper.writeXml(out);
mConditionProviders.writeXml(out, forBackup, userId);
- if (!forBackup || userId == UserHandle.USER_SYSTEM) {
+ if (!forBackup || userId == USER_SYSTEM) {
writeSecureNotificationsPolicy(out);
}
out.endTag(null, TAG_NOTIFICATION_POLICY);
@@ -1273,7 +1282,7 @@
StatusBarNotification sbn = r.getSbn();
cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(),
- sbn.getId(), Notification.FLAG_AUTO_CANCEL,
+ sbn.getId(), FLAG_AUTO_CANCEL,
FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE,
false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null);
nv.recycle();
@@ -1761,9 +1770,50 @@
};
- NotificationManagerPrivate mNotificationManagerPrivate = key -> {
- synchronized (mNotificationLock) {
- return mNotificationsByKey.get(key);
+ NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
+ @Nullable
+ @Override
+ public NotificationRecord getNotificationByKey(String key) {
+ synchronized (mNotificationLock) {
+ return mNotificationsByKey.get(key);
+ }
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ public void timeoutNotification(String key) {
+ boolean foundNotification = false;
+ int uid = 0;
+ int pid = 0;
+ String packageName = null;
+ String tag = null;
+ int id = 0;
+ int userId = 0;
+
+ synchronized (mNotificationLock) {
+ NotificationRecord record = findNotificationByKeyLocked(key);
+ if (record != null) {
+ foundNotification = true;
+ uid = record.getUid();
+ pid = record.getSbn().getInitialPid();
+ packageName = record.getSbn().getPackageName();
+ tag = record.getSbn().getTag();
+ id = record.getSbn().getId();
+ userId = record.getUserId();
+ }
+ }
+ if (foundNotification) {
+ if (lifetimeExtensionRefactor()) {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
+ | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
+ true, userId, REASON_TIMEOUT, null);
+ } else {
+ cancelNotification(uid, pid, packageName, tag, id, 0,
+ FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
+ true, userId, REASON_TIMEOUT, null);
+ }
+ }
}
};
@@ -1893,7 +1943,7 @@
|| action.equals(Intent.ACTION_PACKAGES_UNSUSPENDED)
|| action.equals(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED)) {
int changeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_ALL);
+ USER_ALL);
String pkgList[] = null;
int uidList[] = null;
boolean removingPackage = queryRemove &&
@@ -1942,7 +1992,7 @@
try {
final int enabled = mPackageManager.getApplicationEnabledSetting(
pkgName,
- changeUserId != UserHandle.USER_ALL ? changeUserId :
+ changeUserId != USER_ALL ? changeUserId :
USER_SYSTEM);
if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|| enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
@@ -2055,22 +2105,22 @@
private final class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_BADGING_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
+ = Secure.getUriFor(Secure.NOTIFICATION_BADGING);
private final Uri NOTIFICATION_BUBBLES_URI
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+ = Secure.getUriFor(Secure.NOTIFICATION_BUBBLES);
private final Uri NOTIFICATION_RATE_LIMIT_URI
= Settings.Global.getUriFor(Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE);
private final Uri NOTIFICATION_HISTORY_ENABLED
- = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
+ = Secure.getUriFor(Secure.NOTIFICATION_HISTORY_ENABLED);
private final Uri NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI
= Settings.Global.getUriFor(Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS);
private final Uri LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
- = Settings.Secure.getUriFor(
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ = Secure.getUriFor(
+ Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
private final Uri LOCK_SCREEN_SHOW_NOTIFICATIONS
- = Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
+ = Secure.getUriFor(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
private final Uri SHOW_NOTIFICATION_SNOOZE
- = Settings.Secure.getUriFor(Settings.Secure.SHOW_NOTIFICATION_SNOOZE);
+ = Secure.getUriFor(Secure.SHOW_NOTIFICATION_SNOOZE);
SettingsObserver(Handler handler) {
super(handler);
@@ -2079,27 +2129,31 @@
void observe() {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_RATE_LIMIT_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_BUBBLES_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_HISTORY_ENABLED,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(LOCK_SCREEN_SHOW_NOTIFICATIONS,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
resolver.registerContentObserver(SHOW_NOTIFICATION_SNOOZE,
- false, this, UserHandle.USER_ALL);
+ false, this, USER_ALL);
update(null);
}
+ void destroy() {
+ getContext().getContentResolver().unregisterContentObserver(this);
+ }
+
@Override public void onChange(boolean selfChange, Uri uri, int userId) {
update(uri);
}
@@ -2131,7 +2185,7 @@
mPreferencesHelper.updateLockScreenShowNotifications();
}
if (SHOW_NOTIFICATION_SNOOZE.equals(uri)) {
- final boolean snoozeEnabled = Settings.Secure.getIntForUser(resolver,
+ final boolean snoozeEnabled = Secure.getIntForUser(resolver,
Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT)
!= 0;
if (!snoozeEnabled) {
@@ -2144,8 +2198,8 @@
ContentResolver resolver = getContext().getContentResolver();
if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
mArchive.updateHistoryEnabled(userId,
- Settings.Secure.getIntForUser(resolver,
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+ Secure.getIntForUser(resolver,
+ Secure.NOTIFICATION_HISTORY_ENABLED, 0,
userId) == 1);
// note: this setting is also handled in NotificationHistoryManager
}
@@ -2229,6 +2283,11 @@
}
@VisibleForTesting
+ void setLockPatternUtils(LockPatternUtils lockUtils) {
+ mLockUtils = lockUtils;
+ }
+
+ @VisibleForTesting
ShortcutHelper getShortcutHelper() {
return mShortcutHelper;
}
@@ -2400,7 +2459,7 @@
getContext().sendBroadcastAsUser(
new Intent(ACTION_INTERRUPTION_FILTER_CHANGED_INTERNAL)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT),
- UserHandle.ALL, permission.MANAGE_NOTIFICATIONS);
+ UserHandle.ALL, android.Manifest.permission.MANAGE_NOTIFICATIONS);
synchronized (mNotificationLock) {
updateInterruptionFilterLocked();
}
@@ -2456,15 +2515,14 @@
mAppOps,
mUserProfiles,
mShowReviewPermissionsNotification);
- mRankingHelper = new RankingHelper(getContext(),
- mRankingHandler,
- mPreferencesHelper,
- mZenModeHelper,
- mUsageStats,
- extractorNames);
+ mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
+ mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper = new TimeToLiveHelper(mNotificationManagerPrivate, getContext());
+ }
// This is a ManagedServices object that keeps track of the listeners.
mListeners = notificationListeners;
@@ -2551,10 +2609,12 @@
getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null,
null);
- IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
- timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
- getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
+ if (!Flags.allNotifsNeedTtl()) {
+ IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_EXPORTED_UNAUDITED);
+ }
IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
@@ -2567,15 +2627,16 @@
ReviewNotificationPermissionsReceiver.getFilter(),
Context.RECEIVER_NOT_EXPORTED);
- mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null,
- new AppOpsManager.OnOpChangedInternalListener() {
- @Override
- public void onOpChanged(@NonNull String op, @NonNull String packageName,
- int userId) {
- mHandler.post(
- () -> handleNotificationPermissionChange(packageName, userId));
- }
- });
+ mAppOpsListener = new AppOpsManager.OnOpChangedInternalListener() {
+ @Override
+ public void onOpChanged(@NonNull String op, @NonNull String packageName,
+ int userId) {
+ mHandler.post(
+ () -> handleNotificationPermissionChange(packageName, userId));
+ }
+ };
+
+ mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener);
}
/**
@@ -2584,10 +2645,26 @@
public void onDestroy() {
getContext().unregisterReceiver(mIntentReceiver);
getContext().unregisterReceiver(mPackageIntentReceiver);
- getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.destroy();
+ } else {
+ getContext().unregisterReceiver(mNotificationTimeoutReceiver);
+ }
getContext().unregisterReceiver(mRestoreReceiver);
getContext().unregisterReceiver(mLocaleChangeReceiver);
+ mSettingsObserver.destroy();
+ mRoleObserver.destroy();
+ if (mShortcutHelper != null) {
+ mShortcutHelper.destroy();
+ }
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+ mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
+ mAppOps.stopWatchingMode(mAppOpsListener);
+ mAlarmManager.cancelAll();
+
if (mDeviceConfigChangedListener != null) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
}
@@ -2842,7 +2919,10 @@
bubbsExtractor.setShortcutHelper(mShortcutHelper);
}
registerNotificationPreferencesPullers();
- new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
+ if (mLockUtils == null) {
+ mLockUtils = new LockPatternUtils(getContext());
+ }
+ mLockUtils.registerStrongAuthTracker(mStrongAuthTracker);
mAttentionHelper.onSystemReady();
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
// This observer will force an update when observe is called, causing us to
@@ -2861,6 +2941,15 @@
mZenModeHelper.setDeviceEffectsApplier(
new DefaultDeviceEffectsApplier(getContext()));
}
+ List<ModuleInfo> moduleInfoList =
+ mPackageManagerClient.getInstalledModules(
+ PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
+ // Cache adservices module info
+ for (ModuleInfo mi : moduleInfoList) {
+ if (Objects.equals(mi.getApexModuleName(), ADSERVICES_MODULE_PKG_NAME)) {
+ mAdservicesModuleInfo = mi;
+ }
+ }
} else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
} else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
@@ -2952,7 +3041,7 @@
void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel,
boolean fromListener) {
- if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
+ if (channel.getImportance() == IMPORTANCE_NONE) {
// cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0,
UserHandle.getUserId(uid), REASON_CHANNEL_BANNED
@@ -3217,14 +3306,14 @@
| SUPPRESSED_EFFECT_SCREEN_OFF);
// set the deprecated effects according to the new more specific effects
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_PEEK) != 0) {
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_PEEK) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_ON;
}
- if ((newSuppressedVisualEffects & Policy.SUPPRESSED_EFFECT_LIGHTS) != 0
+ if ((newSuppressedVisualEffects & SUPPRESSED_EFFECT_LIGHTS) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
+ & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0
&& (newSuppressedVisualEffects
- & Policy.SUPPRESSED_EFFECT_AMBIENT) != 0) {
+ & SUPPRESSED_EFFECT_AMBIENT) != 0) {
newSuppressedVisualEffects |= SUPPRESSED_EFFECT_SCREEN_OFF;
}
} else {
@@ -3292,7 +3381,7 @@
if (n.extras != null) {
title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
if (title == null) {
- title = n.extras.getCharSequence(Notification.EXTRA_TITLE_BIG);
+ title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
}
}
return title == null ? getContext().getResources().getString(
@@ -3311,9 +3400,9 @@
if (nb.getStyle() instanceof Notification.BigTextStyle) {
text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
- } else if (nb.getStyle() instanceof Notification.MessagingStyle) {
- Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
- final List<Notification.MessagingStyle.Message> messages = ms.getMessages();
+ } else if (nb.getStyle() instanceof MessagingStyle) {
+ MessagingStyle ms = (MessagingStyle) nb.getStyle();
+ final List<MessagingStyle.Message> messages = ms.getMessages();
if (messages != null && messages.size() > 0) {
text = messages.get(messages.size() - 1).getText();
}
@@ -3364,7 +3453,7 @@
}
private int getRealUserId(int userId) {
- return userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
+ return userId == USER_ALL ? USER_SYSTEM : userId;
}
private ToastRecord getToastRecord(int uid, int pid, String packageName, boolean isSystemToast,
@@ -4096,8 +4185,8 @@
public void createConversationNotificationChannelForPackage(String pkg, int uid,
NotificationChannel parentChannel, String conversationId) {
enforceSystemOrSystemUI("only system can call this");
- Preconditions.checkNotNull(parentChannel);
- Preconditions.checkNotNull(conversationId);
+ checkNotNull(parentChannel);
+ checkNotNull(conversationId);
String parentId = parentChannel.getId();
NotificationChannel conversationChannel = parentChannel;
conversationChannel.setId(String.format(
@@ -4542,7 +4631,7 @@
int uid = Binder.getCallingUid();
ArrayList<Integer> currentUsers = new ArrayList<>();
- currentUsers.add(UserHandle.USER_ALL);
+ currentUsers.add(USER_ALL);
Binder.withCleanCallingIdentity(() -> {
for (int user : mUm.getProfileIds(ActivityManager.getCurrentUser(), false)) {
currentUsers.add(user);
@@ -4798,7 +4887,7 @@
* Register a listener binder directly with the notification manager.
*
* Only works with system callers. Apps should extend
- * {@link android.service.notification.NotificationListenerService}.
+ * {@link NotificationListenerService}.
*/
@Override
public void registerListener(final INotificationListener listener,
@@ -4855,7 +4944,7 @@
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL &&
+ if (userId != info.userid && userId != USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -4972,7 +5061,7 @@
NotificationRecord r = mNotificationsByKey.get(keys[i]);
if (r == null) continue;
final int userId = r.getSbn().getUserId();
- if (userId != info.userid && userId != UserHandle.USER_ALL
+ if (userId != info.userid && userId != USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
continue;
}
@@ -5639,7 +5728,7 @@
}
private void enforcePolicyAccess(int uid, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5670,7 +5759,7 @@
}
private void enforcePolicyAccess(String pkg, String method) {
- if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
+ if (PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
return;
}
@@ -5691,7 +5780,7 @@
try {
uid = getContext().getPackageManager().getPackageUidAsUser(pkg,
UserHandle.getCallingUserId());
- if (PackageManager.PERMISSION_GRANTED == checkComponentPermission(
+ if (PERMISSION_GRANTED == checkComponentPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS, uid,
-1, true)) {
return true;
@@ -5886,7 +5975,7 @@
/**
* Sets the notification policy. Apps that target API levels below
- * {@link android.os.Build.VERSION_CODES#P} cannot change user-designated values to
+ * {@link Build.VERSION_CODES#P} cannot change user-designated values to
* allow or disallow {@link Policy#PRIORITY_CATEGORY_ALARMS},
* {@link Policy#PRIORITY_CATEGORY_SYSTEM} and
* {@link Policy#PRIORITY_CATEGORY_MEDIA} from bypassing dnd
@@ -6254,7 +6343,7 @@
@Override
public void setPrivateNotificationsAllowed(boolean allow) {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6275,7 +6364,7 @@
@Override
public boolean getPrivateNotificationsAllowed() {
- if (PackageManager.PERMISSION_GRANTED
+ if (PERMISSION_GRANTED
!= getContext().checkCallingPermission(CONTROL_KEYGUARD_SECURE_NOTIFICATIONS)) {
throw new SecurityException(
"Requires CONTROL_KEYGUARD_SECURE_NOTIFICATIONS permission");
@@ -6563,9 +6652,9 @@
// Add summary
final ApplicationInfo appInfo =
adjustedSbn.getNotification().extras.getParcelable(
- Notification.EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+ EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
final Bundle extras = new Bundle();
- extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
+ extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, appInfo);
final String channelId = notificationRecord.getChannel().getId();
final Notification summaryNotification =
@@ -6859,6 +6948,11 @@
if (!zenOnly) {
pw.println("\n Usage Stats:");
mUsageStats.dump(pw, " ", filter);
+
+ if (Flags.allNotifsNeedTtl()) {
+ pw.println("\n TimeToLive alarms:");
+ mTtlHelper.dump(pw, " ");
+ }
}
}
}
@@ -7455,7 +7549,7 @@
throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
- (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
+ (userId == USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
@@ -7570,7 +7664,7 @@
// Enforce NO_CLEAR flag on MediaStyle notification for apps with targetSdk >= V.
if (CompatChanges.isChangeEnabled(ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION,
notificationUid)) {
- notification.flags |= Notification.FLAG_NO_CLEAR;
+ notification.flags |= FLAG_NO_CLEAR;
}
}
@@ -7605,13 +7699,27 @@
private boolean canBeNonDismissible(ApplicationInfo ai, Notification notification) {
return notification.isMediaNotification() || isEnterpriseExempted(ai)
|| notification.isStyle(Notification.CallStyle.class)
- || isDefaultSearchSelectorPackage(ai.packageName);
+ || isDefaultSearchSelectorPackage(ai.packageName)
+ || isDefaultAdservicesPackage(ai.packageName);
}
private boolean isDefaultSearchSelectorPackage(String pkg) {
return Objects.equals(mDefaultSearchSelectorPkg, pkg);
}
+ private boolean isDefaultAdservicesPackage(String pkg) {
+ if (mAdservicesModuleInfo == null) {
+ return false;
+ }
+ // Handles the special package structure for mainline modules
+ for (String apkName : mAdservicesModuleInfo.getApkInApexPackageNames()) {
+ if (Objects.equals(apkName, pkg)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean isEnterpriseExempted(ApplicationInfo ai) {
// Check if the app is an organization admin app
// TODO(b/234609037): Replace with new DPM APIs to check if organization admin
@@ -7622,7 +7730,7 @@
// Check if an app has been given system exemption
return mSystemExemptFromDismissal && mAppOps.checkOpNoThrow(
AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
- ai.packageName) == AppOpsManager.MODE_ALLOWED;
+ ai.packageName) == MODE_ALLOWED;
}
private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
@@ -7727,7 +7835,7 @@
// Enqueue will trigger resort & flag is updated that way.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
mHandler.post(
- new NotificationManagerService.EnqueueNotificationRunnable(
+ new EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
mPostNotificationTrackerFactory.newTracker(null)));
}
@@ -7749,7 +7857,7 @@
@VisibleForTesting
int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) {
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
// posted from app A on behalf of app A
@@ -8008,7 +8116,7 @@
final String pkg = r.getSbn().getPackageName();
final int callingUid = r.getSbn().getUid();
return mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
- || r.getImportance() == NotificationManager.IMPORTANCE_NONE;
+ || r.getImportance() == IMPORTANCE_NONE;
}
protected class SnoozeNotificationRunnable implements Runnable {
@@ -8347,7 +8455,11 @@
}
mEnqueuedNotifications.add(r);
- scheduleTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.scheduleTimeoutLocked(r, SystemClock.elapsedRealtime());
+ } else {
+ scheduleTimeoutLocked(r);
+ }
final StatusBarNotification n = r.getSbn();
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
@@ -8567,7 +8679,7 @@
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
- NotificationListenerService.REASON_ERROR, r.getStats());
+ REASON_ERROR, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -8935,7 +9047,7 @@
try {
isExemptFromRateLimiting = mPackageManager.checkPermission(
android.Manifest.permission.UNLIMITED_TOASTS, pkg, userId)
- == PackageManager.PERMISSION_GRANTED;
+ == PERMISSION_GRANTED;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to connect with package manager");
}
@@ -9460,7 +9572,11 @@
int rank, int count, boolean wasPosted, String listenerName,
@ElapsedRealtimeLong long cancellationElapsedTimeMs) {
final String canceledKey = r.getKey();
- cancelScheduledTimeoutLocked(r);
+ if (Flags.allNotifsNeedTtl()) {
+ mTtlHelper.cancelScheduledTimeoutLocked(r);
+ } else {
+ cancelScheduledTimeoutLocked(r);
+ }
// Record caller.
recordCallerLocked(r);
@@ -9637,7 +9753,7 @@
// Uri, not when removing an individual listener.
revokeUriPermission(permissionOwner, uri,
UserHandle.getUserId(oldRecord.getUid()),
- null, UserHandle.USER_ALL);
+ null, USER_ALL);
}
}
}
@@ -9735,9 +9851,9 @@
} else {
return
// looking for USER_ALL notifications? match everything
- userId == UserHandle.USER_ALL
+ userId == USER_ALL
// a notification sent to USER_ALL matches any query
- || r.getUserId() == UserHandle.USER_ALL
+ || r.getUserId() == USER_ALL
// an exact user match
|| r.getUserId() == userId;
}
@@ -9816,7 +9932,7 @@
continue;
}
// Don't remove notifications to all, if there's no package name specified
- if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == UserHandle.USER_ALL) {
+ if (nullPkgIndicatesUserSwitch && pkg == null && r.getUserId() == USER_ALL) {
continue;
}
if (!flagChecker.apply(r.getFlags())) {
@@ -10314,7 +10430,7 @@
return false;
}
- if (userId == UserHandle.USER_ALL) {
+ if (userId == USER_ALL) {
userId = USER_SYSTEM;
}
@@ -10537,7 +10653,7 @@
if (requiredPermission != null) {
try {
if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
- != PackageManager.PERMISSION_GRANTED) {
+ != PERMISSION_GRANTED) {
canUseManagedServices = false;
}
} catch (RemoteException e) {
@@ -10663,7 +10779,7 @@
c.caption = "notification assistant";
c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_ASSISTANT;
c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
c.clientLabel = R.string.notification_ranker_binding_label;
@@ -11259,7 +11375,7 @@
c.caption = "notification listener";
c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
c.xmlTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
- c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
+ c.secureSettingName = Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
c.clientLabel = R.string.notification_listener_binding_label;
@@ -11667,7 +11783,7 @@
// Managed Services.
if (info.isSystemUi() && old != null && old.getNotification() != null
&& (old.getNotification().flags
- & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+ & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
break;
@@ -11696,8 +11812,8 @@
continue;
}
// Grant access before listener is notified
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
mPackageManagerInternal.grantImplicitAccess(
@@ -11846,8 +11962,8 @@
continue;
}
// Grant or revoke access synchronously
- final int targetUserId = (info.userid == UserHandle.USER_ALL)
- ? UserHandle.USER_SYSTEM : info.userid;
+ final int targetUserId = (info.userid == USER_ALL)
+ ? USER_SYSTEM : info.userid;
if (grant) {
// Grant permissions by passing arguments as if the notification is new.
updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
@@ -11919,7 +12035,7 @@
}
// Revoke access after all listeners have been updated
- mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
+ mHandler.post(() -> updateUriPermissions(null, r, null, USER_SYSTEM));
}
/**
@@ -12079,7 +12195,7 @@
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
@@ -12102,7 +12218,7 @@
reason = REASON_LISTENER_CANCEL;
}
listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (removed): " + info, ex);
@@ -12114,7 +12230,7 @@
final INotificationListener listener = (INotificationListener) info.service;
try {
listener.onNotificationRankingUpdate(rankingUpdate);
- } catch (android.os.DeadObjectException ex) {
+ } catch (DeadObjectException ex) {
Slog.wtf(TAG, "unable to notify listener (ranking update): " + info, ex);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (ranking update): " + info, ex);
@@ -12333,6 +12449,10 @@
mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
}
+ void destroy() {
+ mRm.removeOnRoleHoldersChangedListenerAsUser(this, UserHandle.ALL);
+ }
+
@VisibleForTesting
public boolean isApprovedPackageForRoleForUser(String role, String pkg, int userId) {
return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
@@ -12621,7 +12741,7 @@
.setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.setStyle(new Notification.BigTextStyle())
- .setFlag(Notification.FLAG_NO_CLEAR, true)
+ .setFlag(FLAG_NO_CLEAR, true)
.setAutoCancel(true)
.addAction(remindMe)
.addAction(dismiss)
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 97d2620..c69bead 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -79,6 +79,7 @@
import java.io.PrintWriter;
import java.lang.reflect.Array;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -600,8 +601,7 @@
pw.println(prefix + "headsUpContentView="
+ formatRemoteViews(notification.headsUpContentView));
pw.println(prefix + String.format("color=0x%08x", notification.color));
- pw.println(prefix + "timeout="
- + TimeUtils.formatForLogging(notification.getTimeoutAfter()));
+ pw.println(prefix + "timeout=" + Duration.ofMillis(notification.getTimeoutAfter()));
if (notification.actions != null && notification.actions.length > 0) {
pw.println(prefix + "actions={");
final int N = notification.actions.length;
diff --git a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
index 24c1d59..f0358d1 100644
--- a/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationSignalExtractor.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.content.Context;
+import com.android.internal.compat.IPlatformCompat;
/**
* Extracts signals that will be useful to the {@link NotificationComparator} and caches them
@@ -52,4 +53,6 @@
* DND.
*/
void setZenHelper(ZenModeHelper helper);
+
+ default void setCompatChangeLogger(IPlatformCompat platformCompat){};
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 50ca984..461bd9c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1387,8 +1387,7 @@
public void updateFixedImportance(List<UserInfo> users) {
for (UserInfo user : users) {
List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY),
- user.getUserHandle().getIdentifier());
+ 0, user.getUserHandle().getIdentifier());
for (PackageInfo pi : packages) {
boolean fixed = mPermissionHelper.isPermissionFixed(
pi.packageName, user.getUserHandle().getIdentifier());
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 68e0eaa..7756801 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
+import static android.app.Flags.restrictAudioAttributesAlarm;
+import static android.app.Flags.restrictAudioAttributesCall;
+import static android.app.Flags.restrictAudioAttributesMedia;
import static android.app.Flags.sortSectionByTime;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.text.TextUtils.formatSimple;
@@ -27,6 +30,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.compat.IPlatformCompat;
import com.android.tools.r8.keepanno.annotations.KeepItemKind;
import com.android.tools.r8.keepanno.annotations.KeepTarget;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
@@ -56,7 +60,8 @@
methodName = "<init>")
})
public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
- ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
+ ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames,
+ IPlatformCompat platformCompat) {
mContext = context;
mRankingHandler = rankingHandler;
if (sortSectionByTime()) {
@@ -75,6 +80,10 @@
extractor.initialize(mContext, usageStats);
extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
+ if (restrictAudioAttributesAlarm() || restrictAudioAttributesMedia()
+ || restrictAudioAttributesCall()) {
+ extractor.setCompatChangeLogger(platformCompat);
+ }
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {
Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index fc106b8..86dcecf 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -287,4 +287,11 @@
}
}
}
+
+ void destroy() {
+ if (mLauncherAppsCallbackRegistered) {
+ mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+ mLauncherAppsCallbackRegistered = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/TimeToLiveHelper.java b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
new file mode 100644
index 0000000..2facab7
--- /dev/null
+++ b/services/core/java/com/android/server/notification/TimeToLiveHelper.java
@@ -0,0 +1,175 @@
+/*
+ * 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.notification;
+
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.PackageManagerService;
+
+import java.io.PrintWriter;
+import java.util.TreeSet;
+
+/**
+ * Handles canceling notifications when their time to live expires
+ */
+@FlaggedApi(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+public class TimeToLiveHelper {
+ private static final String TAG = TimeToLiveHelper.class.getSimpleName();
+ private static final String ACTION = "com.android.server.notification.TimeToLiveHelper";
+
+ private static final int REQUEST_CODE_TIMEOUT = 1;
+ private static final String SCHEME_TIMEOUT = "timeout";
+ static final String EXTRA_KEY = "key";
+ private final Context mContext;
+ private final NotificationManagerPrivate mNm;
+ private final AlarmManager mAm;
+
+ @VisibleForTesting
+ final TreeSet<Pair<Long, String>> mKeys;
+
+ public TimeToLiveHelper(NotificationManagerPrivate nm, Context context) {
+ mContext = context;
+ mNm = nm;
+ mAm = context.getSystemService(AlarmManager.class);
+ mKeys = new TreeSet<>((left, right) -> Long.compare(left.first, right.first));
+
+ IntentFilter timeoutFilter = new IntentFilter(ACTION);
+ timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
+ mContext.registerReceiver(mNotificationTimeoutReceiver, timeoutFilter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ void destroy() {
+ mContext.unregisterReceiver(mNotificationTimeoutReceiver);
+ }
+
+ void dump(PrintWriter pw, String indent) {
+ pw.println(indent + "mKeys " + mKeys);
+ }
+
+ private @NonNull PendingIntent getAlarmPendingIntent(String nextKey, int flags) {
+ flags |= PendingIntent.FLAG_IMMUTABLE;
+ return PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_TIMEOUT,
+ new Intent(ACTION)
+ .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
+ .setData(new Uri.Builder()
+ .scheme(SCHEME_TIMEOUT)
+ .appendPath(nextKey)
+ .build())
+ .putExtra(EXTRA_KEY, nextKey)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ flags);
+ }
+
+ @VisibleForTesting
+ void scheduleTimeoutLocked(NotificationRecord record, long currentTime) {
+ removeMatchingEntry(record.getKey());
+
+ final long timeoutAfter = currentTime + record.getNotification().getTimeoutAfter();
+ if (record.getNotification().getTimeoutAfter() > 0) {
+ final Long currentEarliestTime = mKeys.isEmpty() ? null : mKeys.first().first;
+
+ // Maybe replace alarm with an earlier one
+ if (currentEarliestTime == null || timeoutAfter < currentEarliestTime) {
+ if (currentEarliestTime != null) {
+ cancelFirstAlarm();
+ }
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ maybeScheduleFirstAlarm();
+ } else {
+ mKeys.add(Pair.create(timeoutAfter, record.getKey()));
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void cancelScheduledTimeoutLocked(NotificationRecord record) {
+ removeMatchingEntry(record.getKey());
+ }
+
+ private void removeMatchingEntry(String key) {
+ if (!mKeys.isEmpty() && key.equals(mKeys.first().second)) {
+ // cancel the first alarm, remove the first entry, maybe schedule the alarm for the new
+ // first entry
+ cancelFirstAlarm();
+ mKeys.remove(mKeys.first());
+ maybeScheduleFirstAlarm();
+ } else {
+ // just remove the entry
+ Pair<Long, String> trackedPair = null;
+ for (Pair<Long, String> entry : mKeys) {
+ if (key.equals(entry.second)) {
+ trackedPair = entry;
+ break;
+ }
+ }
+ if (trackedPair != null) {
+ mKeys.remove(trackedPair);
+ }
+ }
+ }
+
+ private void cancelFirstAlarm() {
+ final PendingIntent pi = getAlarmPendingIntent(mKeys.first().second, FLAG_CANCEL_CURRENT);
+ mAm.cancel(pi);
+ }
+
+ private void maybeScheduleFirstAlarm() {
+ if (!mKeys.isEmpty()) {
+ final PendingIntent piNewFirst = getAlarmPendingIntent(mKeys.first().second,
+ FLAG_UPDATE_CURRENT);
+ mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mKeys.first().first, piNewFirst);
+ }
+ }
+
+ @VisibleForTesting
+ final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ if (ACTION.equals(action)) {
+ Pair<Long, String> earliest = mKeys.first();
+ String key = intent.getStringExtra(EXTRA_KEY);
+ if (!earliest.second.equals(key)) {
+ Slog.wtf(TAG, "Alarm triggered but wasn't the earliest we were tracking");
+ }
+ removeMatchingEntry(key);
+ mNm.timeoutNotification(earliest.second);
+ }
+ }
+ };
+}
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 4c653f6..fe9c3f2 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -50,6 +50,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService;
import android.content.pm.LauncherApps;
+import android.content.pm.LauncherApps.ShortcutChangeCallback;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -151,6 +152,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
@@ -320,12 +322,11 @@
private final Handler mHandler;
- @GuardedBy("mLock")
- private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
+ private final CopyOnWriteArrayList<ShortcutChangeListener> mListeners =
+ new CopyOnWriteArrayList<>();
- @GuardedBy("mLock")
- private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
- new ArrayList<>(1);
+ private final CopyOnWriteArrayList<ShortcutChangeCallback> mShortcutChangeCallbacks =
+ new CopyOnWriteArrayList<>();
private final AtomicLong mRawLastResetTime = new AtomicLong(0);
@@ -1841,18 +1842,11 @@
@UserIdInt final int userId) {
return () -> {
try {
- final ArrayList<ShortcutChangeListener> copy;
- synchronized (mLock) {
- if (!isUserUnlockedL(userId)) {
- return;
- }
-
- copy = new ArrayList<>(mListeners);
+ if (!isUserUnlockedL(userId)) {
+ return;
}
// Note onShortcutChanged() needs to be called with the system service permissions.
- for (int i = copy.size() - 1; i >= 0; i--) {
- copy.get(i).onShortcutChanged(packageName, userId);
- }
+ mListeners.forEach(listener -> listener.onShortcutChanged(packageName, userId));
} catch (Exception ignore) {
}
};
@@ -1867,22 +1861,17 @@
final UserHandle user = UserHandle.of(userId);
injectPostToHandler(() -> {
try {
- final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
- synchronized (mLock) {
- if (!isUserUnlockedL(userId)) {
- return;
- }
-
- copy = new ArrayList<>(mShortcutChangeCallbacks);
+ if (!isUserUnlockedL(userId)) {
+ return;
}
- for (int i = copy.size() - 1; i >= 0; i--) {
+ mShortcutChangeCallbacks.forEach(callback -> {
if (!CollectionUtils.isEmpty(changedList)) {
- copy.get(i).onShortcutsAddedOrUpdated(packageName, changedList, user);
+ callback.onShortcutsAddedOrUpdated(packageName, changedList, user);
}
if (!CollectionUtils.isEmpty(removedList)) {
- copy.get(i).onShortcutsRemoved(packageName, removedList, user);
+ callback.onShortcutsRemoved(packageName, removedList, user);
}
- }
+ });
} catch (Exception ignore) {
}
});
@@ -3425,17 +3414,13 @@
@Override
public void addListener(@NonNull ShortcutChangeListener listener) {
- synchronized (mLock) {
- mListeners.add(Objects.requireNonNull(listener));
- }
+ mListeners.add(Objects.requireNonNull(listener));
}
@Override
public void addShortcutChangeCallback(
@NonNull LauncherApps.ShortcutChangeCallback callback) {
- synchronized (mLock) {
- mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
- }
+ mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
}
@Override
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index b237ca2..84ed87a 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -76,6 +76,8 @@
private static final boolean DEFAULT_FEATURE_ENABLED = true;
private boolean mIsEnabled;
+ // Defaults to true, we only want to disable if this is specifically requested.
+ private boolean mEnabledOverride = true;
private int mSensorMaxLatencyMicros;
@@ -240,6 +242,7 @@
pw.println(" mZAccelerationThreshold=" + mZAccelerationThreshold);
pw.println(" mAccelerationThreshold=" + mAccelerationThreshold);
pw.println(" mTimeThreshold=" + mTimeThreshold);
+ pw.println(" mEnabledOverride=" + mEnabledOverride);
}
@Override
@@ -336,10 +339,9 @@
}
private boolean isEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
- DEFAULT_FEATURE_ENABLED)
- && mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_flipToScreenOffEnabled);
+ return mEnabledOverride && DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_FEATURE_ENABLED, DEFAULT_FEATURE_ENABLED) && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_flipToScreenOffEnabled);
}
private float getAccelerationThreshold() {
@@ -450,6 +452,15 @@
}
/**
+ * Allows detector to be enabled & disabled.
+ * @param enabled whether to enable detector.
+ */
+ public void setEnabledOverride(boolean enabled) {
+ mEnabledOverride = enabled;
+ mIsEnabled = isEnabled();
+ }
+
+ /**
* Sets how much screen on time might be saved as a result of this detector. Currently used for
* logging purposes.
*/
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 53863aa..eb1f720 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -622,7 +622,6 @@
// Value we store for tracking face down behavior.
@VisibleForTesting
boolean mIsFaceDown = false;
- private boolean mUseFaceDownDetector = true;
private long mLastFlipTime = 0L;
// The screen brightness setting override from the window manager
@@ -3254,7 +3253,7 @@
mScreenTimeoutOverridePolicy.getScreenTimeoutOverrideLocked(
mWakeLockSummary, screenOffTimeout);
}
- if (mIsFaceDown && mUseFaceDownDetector) {
+ if (mIsFaceDown) {
shortestScreenOffTimeout = Math.min(screenDimDuration, shortestScreenOffTimeout);
}
@@ -4702,7 +4701,6 @@
pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
pw.println(" mLastFlipTime=" + mLastFlipTime);
pw.println(" mIsFaceDown=" + mIsFaceDown);
- pw.println(" mUseFaceDownDetector=" + mUseFaceDownDetector);
pw.println();
pw.println("Settings and Configuration:");
@@ -6927,7 +6925,7 @@
public void setUseFaceDownDetector(boolean enable) {
final long ident = Binder.clearCallingIdentity();
try {
- mUseFaceDownDetector = enable;
+ mFaceDownDetector.setEnabledOverride(enable);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index d060c7c..54cb9c9 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -4885,7 +4885,6 @@
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
- aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
if (historyName == null) {
historyName = name;
}
@@ -5205,20 +5204,14 @@
}
@GuardedBy("this")
- void aggregateLastWakeupUptimeLocked(long elapsedRealtimeMs, long uptimeMs) {
+ public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
if (mLastWakeupReason != null) {
long deltaUptimeMs = uptimeMs - mLastWakeupUptimeMs;
SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds
mFrameworkStatsLogger.kernelWakeupReported(deltaUptimeMs * 1000, mLastWakeupReason,
mLastWakeupElapsedTimeMs);
- mLastWakeupReason = null;
}
- }
-
- @GuardedBy("this")
- public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
- aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
mLastWakeupReason = reason;
mLastWakeupUptimeMs = uptimeMs;
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
index b1b2cc9..f53a1b0 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java
@@ -49,7 +49,6 @@
private static final long ENERGY_UNSPECIFIED = -1;
private static final int DEFAULT_CPU_POWER_BRACKETS = 3;
private static final int DEFAULT_CPU_POWER_BRACKETS_PER_ENERGY_CONSUMER = 2;
- private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
interface Injector {
Handler getHandler();
@@ -76,7 +75,6 @@
private CpuScalingPolicies mCpuScalingPolicies;
private PowerProfile mPowerProfile;
private KernelCpuStatsReader mKernelCpuStatsReader;
- private PowerStatsUidResolver mUidResolver;
private ConsumedEnergyRetriever mConsumedEnergyRetriever;
private IntSupplier mVoltageSupplier;
private int mDefaultCpuPowerBrackets;
@@ -97,7 +95,8 @@
private long[] mLastConsumedEnergyUws;
public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ injector.getClock());
mInjector = injector;
}
@@ -113,7 +112,6 @@
mCpuScalingPolicies = mInjector.getCpuScalingPolicies();
mPowerProfile = mInjector.getPowerProfile();
mKernelCpuStatsReader = mInjector.getKernelCpuStatsReader();
- mUidResolver = mInjector.getUidResolver();
mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
mVoltageSupplier = mInjector.getVoltageSupplier();
mDefaultCpuPowerBrackets = mInjector.getDefaultCpuPowerBrackets();
@@ -421,7 +419,8 @@
boolean nonzero = false;
for (int bracket = powerBracketCount - 1; bracket >= 0; bracket--) {
- long delta = timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket];
+ long delta = Math.max(0,
+ timeByPowerBracket[bracket] - uidStats.timeByPowerBracket[bracket]);
if (delta != 0) {
nonzero = true;
}
@@ -447,6 +446,12 @@
}
}
+ @Override
+ protected void onUidRemoved(int uid) {
+ super.onUidRemoved(uid);
+ mUidStats.remove(uid);
+ }
+
/**
* Native class that retrieves CPU stats from the kernel.
*/
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 8c154e4..7bc6817 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -89,7 +89,6 @@
private PowerStats mPowerStats;
private long[] mDeviceStats;
- private PowerStatsUidResolver mPowerStatsUidResolver;
private volatile TelephonyManager mTelephonyManager;
private LongSupplier mCallDurationSupplier;
private LongSupplier mScanDurationSupplier;
@@ -106,7 +105,8 @@
private long mLastScanDuration;
public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) {
- super(injector.getHandler(), throttlePeriodMs, injector.getClock());
+ super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(),
+ injector.getClock());
mInjector = injector;
}
@@ -130,7 +130,6 @@
return false;
}
- mPowerStatsUidResolver = mInjector.getUidResolver();
mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
mVoltageSupplier = mInjector.getVoltageSupplier();
@@ -310,7 +309,7 @@
continue;
}
- int uid = mPowerStatsUidResolver.mapUid(uidDelta.getUid());
+ int uid = mUidResolver.mapUid(uidDelta.getUid());
long[] stats = mPowerStats.uidStats.get(uid);
if (stats == null) {
stats = new long[mLayout.getUidStatsArrayLength()];
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index 5dd11db..b82c021 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -53,6 +53,7 @@
private static final int MILLIVOLTS_PER_VOLT = 1000;
private static final long POWER_STATS_ENERGY_CONSUMERS_TIMEOUT = 20000;
private final Handler mHandler;
+ protected final PowerStatsUidResolver mUidResolver;
protected final Clock mClock;
private final long mThrottlePeriodMs;
private final Runnable mCollectAndDeliverStats = this::collectAndDeliverStats;
@@ -63,9 +64,25 @@
@SuppressWarnings("unchecked")
private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
- public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
+ public PowerStatsCollector(Handler handler, long throttlePeriodMs,
+ PowerStatsUidResolver uidResolver, Clock clock) {
mHandler = handler;
mThrottlePeriodMs = throttlePeriodMs;
+ mUidResolver = uidResolver;
+ mUidResolver.addListener(new PowerStatsUidResolver.Listener() {
+ @Override
+ public void onIsolatedUidAdded(int isolatedUid, int parentUid) {
+ }
+
+ @Override
+ public void onBeforeIsolatedUidRemoved(int isolatedUid, int parentUid) {
+ }
+
+ @Override
+ public void onAfterIsolatedUidRemoved(int isolatedUid, int parentUid) {
+ mHandler.post(()->onUidRemoved(isolatedUid));
+ }
+ });
mClock = clock;
}
@@ -203,6 +220,9 @@
done.block();
}
+ protected void onUidRemoved(int uid) {
+ }
+
/** Calculate charge consumption (in microcoulombs) from a given energy and voltage */
protected static long uJtoUc(long deltaEnergyUj, int avgVoltageMv) {
// To overflow, a 3.7V 10000mAh battery would need to completely drain 69244 times
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 10e868d..c1d92cf 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -119,8 +119,13 @@
}
@Override
- public void onStart() {
- publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ }
}
private void notifyTraceur(boolean sessionStolen) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index ed9fa65..3619253 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,10 @@
package com.android.server.vcn.routeselection;
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -38,6 +40,10 @@
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.VcnContext;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.BitSet;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -56,8 +62,51 @@
public class IpSecPacketLossDetector extends NetworkMetricMonitor {
private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+ private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+ // Ignore the packet loss detection result if the expected packet number is smaller than 10.
+ // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
+ // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
@VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PACKET_LOSS_UNAVALAIBLE = -1;
+ static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"PACKET_LOSS_"},
+ value = {
+ PACKET_LOSS_RATE_VALID,
+ PACKET_LOSS_RATE_INVALID,
+ PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
+ })
+ @Target({ElementType.TYPE_USE})
+ private @interface PacketLossResultType {}
+
+ /** Indicates a valid packet loss rate is available */
+ private static final int PACKET_LOSS_RATE_VALID = 0;
+
+ /**
+ * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+ * reasons:
+ *
+ * <ul>
+ * <li>The replay window did not proceed and thus all packets might have been delivered out of
+ * order
+ * <li>The expected received packet number is too small and thus the detection result is not
+ * reliable
+ * <li>There are unexpected errors
+ * </ul>
+ */
+ private static final int PACKET_LOSS_RATE_INVALID = 1;
+
+ /**
+ * The sequence number increase is unusually large and might be caused an intentional leap on
+ * the server's downlink
+ *
+ * <p>Inbound sequence number will not always increase consecutively. During load balancing the
+ * server might add a big leap on the sequence number intentionally. In such case a high packet
+ * loss rate does not always indicate a lossy network
+ */
+ private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
// For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
// significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
@@ -68,8 +117,12 @@
private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+ // By default, there's no maximum limit enforced
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
+
private long mPollIpSecStateIntervalMs;
- private final int mPacketLossRatePercentThreshold;
+ private int mPacketLossRatePercentThreshold;
+ private int mMaxSeqNumIncreasePerSecond;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -108,6 +161,7 @@
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
// Register for system broadcasts to monitor idle mode change
final IntentFilter intentFilter = new IntentFilter();
@@ -172,6 +226,24 @@
return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
+ int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ if (Flags.handleSeqNumLeap() && carrierConfig != null) {
+ maxSeqNumIncrease =
+ carrierConfig.getInt(
+ VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
+ }
+
+ if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
+ return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ }
+
+ return maxSeqNumIncrease;
+ }
+
@Override
protected void onSelectedUnderlyingNetworkChanged() {
if (!isSelectedUnderlyingNetwork()) {
@@ -207,6 +279,11 @@
// The already scheduled event will not be affected. The followup events will be scheduled
// with the new interval
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+
+ if (Flags.handleSeqNumLeap()) {
+ mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+ }
}
@Override
@@ -307,30 +384,40 @@
return;
}
- final int packetLossRate =
+ final PacketLossCalculationResult calculateResult =
mPacketLossCalculator.getPacketLossRatePercentage(
- mLastIpSecTransformState, state, getLogPrefix());
+ mLastIpSecTransformState,
+ state,
+ mMaxSeqNumIncreasePerSecond,
+ getLogPrefix());
- if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
return;
}
final String logMsg =
- "packetLossRate: "
- + packetLossRate
+ "calculateResult: "
+ + calculateResult
+ "% in the past "
+ (state.getTimestampMillis()
- mLastIpSecTransformState.getTimestampMillis())
+ "ms";
mLastIpSecTransformState = state;
- if (packetLossRate < mPacketLossRatePercentThreshold) {
+ if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
logV(logMsg);
+
+ // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
+ // the validation
onValidationResultReceivedInternal(false /* isFailed */);
} else {
logInfo(logMsg);
- onValidationResultReceivedInternal(true /* isFailed */);
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
+ onValidationResultReceivedInternal(true /* isFailed */);
+ }
+
+ // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
if (Flags.validateNetworkOnIpsecLoss()) {
// Trigger re-validation of the underlying network; if it fails, the VCN will
// attempt to migrate away.
@@ -343,9 +430,10 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class PacketLossCalculator {
/** Calculate the packet loss rate between two timestamps */
- public int getPacketLossRatePercentage(
+ public PacketLossCalculationResult getPacketLossRatePercentage(
@NonNull IpSecTransformState oldState,
@NonNull IpSecTransformState newState,
+ int maxSeqNumIncreasePerSecond,
String logPrefix) {
logVIpSecTransform("oldState", oldState, logPrefix);
logVIpSecTransform("newState", newState, logPrefix);
@@ -359,7 +447,23 @@
if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
// The replay window did not proceed and all packets might have been delivered out
// of order
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
+ }
+
+ boolean isUnusualSeqNumLeap = false;
+
+ // Handle sequence number leap
+ if (Flags.handleSeqNumLeap()
+ && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ final long timeDiffMillis =
+ newState.getTimestampMillis() - oldState.getTimestampMillis();
+ final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
+
+ // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
+ // isUnusualSeqNumLeap can never be true.
+ if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
+ isUnusualSeqNumLeap = true;
+ }
}
// Get the expected packet count by assuming there is no packet loss. In this case, SA
@@ -381,15 +485,23 @@
+ " actualPktCntDiff: "
+ actualPktCntDiff);
+ if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+ // The sample size is too small to ensure a reliable detection result
+ return PacketLossCalculationResult.invalid();
+ }
+
if (expectedPktCntDiff < 0
|| expectedPktCntDiff == 0
|| actualPktCntDiff < 0
|| actualPktCntDiff > expectedPktCntDiff) {
logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
}
- return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ return isUnusualSeqNumLeap
+ ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+ : PacketLossCalculationResult.valid(percent);
}
}
@@ -409,4 +521,64 @@
private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
return BitSet.valueOf(state.getReplayBitmap()).cardinality();
}
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class PacketLossCalculationResult {
+ @PacketLossResultType private final int mResultType;
+ private final int mPacketLossRatePercent;
+
+ private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+ mResultType = type;
+ mPacketLossRatePercent = percent;
+ }
+
+ /** Construct an instance that contains a valid packet loss rate */
+ public static PacketLossCalculationResult valid(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+ }
+
+ /** Construct an instance indicating the inability to get a valid packet loss rate */
+ public static PacketLossCalculationResult invalid() {
+ return new PacketLossCalculationResult(
+ PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+ }
+
+ /** Construct an instance indicating that there is an unusual sequence number leap */
+ public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
+ }
+
+ @PacketLossResultType
+ public int getResultType() {
+ return mResultType;
+ }
+
+ public int getPacketLossRatePercent() {
+ return mPacketLossRatePercent;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResultType, mPacketLossRatePercent);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof PacketLossCalculationResult)) {
+ return false;
+ }
+
+ final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+ return mResultType == rhs.mResultType
+ && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+ }
+
+ @Override
+ public String toString() {
+ return "mResultType: "
+ + mResultType
+ + " | mPacketLossRatePercent: "
+ + mPacketLossRatePercent;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index a1b212f..b9b1060 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -272,6 +272,11 @@
}
}
+ protected static void logE(String className, String msgWithPrefix) {
+ Slog.w(className, msgWithPrefix);
+ LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
+ }
+
protected static void logWtf(String className, String msgWithPrefix) {
Slog.wtf(className, msgWithPrefix);
LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 5e34596..a821f545 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -35,6 +35,7 @@
import android.provider.Settings;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
@@ -201,6 +202,7 @@
ActivityManager.getService().killPackageDependents(packageName,
UserHandle.USER_ALL);
} catch (RemoteException e) {
+ Slog.wtf(TAG, "failed to call killPackageDependents for " + packageName, e);
}
}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 532ff98..dcf20f9 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -186,9 +186,12 @@
}
onWebViewProviderChanged(mCurrentWebViewPackage);
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
if (getCurrentWebViewPackage() == null) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index fb338ba..993597e 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -247,9 +247,12 @@
attemptRepair();
}
+ } catch (WebViewPackageMissingException e) {
+ Slog.e(TAG, "Could not find valid WebView package to create relro with", e);
} catch (Throwable t) {
- // Log and discard errors at this stage as we must not crash the system server.
- Slog.e(TAG, "error preparing webview provider from system server", t);
+ // We don't know a case when this should happen but we log and discard errors at this
+ // stage as we must not crash the system server.
+ Slog.wtf(TAG, "error preparing webview provider from system server", t);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 9647a62..521f64f 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -389,7 +389,11 @@
return mCheckedOptions;
}
- /** Returns the {@link Runnable} object to clear options Animation. */
+ /**
+ * Returns the {@link Runnable} object to clear options Animation. Note that the runnable
+ * should not be executed inside a lock because the implementation of runnable holds window
+ * manager's lock.
+ */
@Nullable
public Runnable getClearOptionsAnimationRunnable() {
return mClearOptionsAnimation;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 83745ed..bf094ed 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4260,7 +4260,8 @@
PendingIntentRecord rec = apr.get();
if (rec != null) {
mAtmService.mPendingIntentController.cancelIntentSender(rec,
- false /* cleanActivity */);
+ false /* cleanActivity */,
+ PendingIntentRecord.CANCEL_REASON_HOSTING_ACTIVITY_DESTROYED);
}
}
pendingResults = null;
@@ -4478,6 +4479,7 @@
getDisplayContent().mOpeningApps.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mSnapshotController.onAppRemoved(this);
+ mAtmService.mStartingProcessActivities.remove(this);
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
@@ -6220,6 +6222,10 @@
}
boolean shouldBeVisible() {
+ return shouldBeVisible(false /* ignoringKeyguard */);
+ }
+
+ boolean shouldBeVisible(boolean ignoringKeyguard) {
final Task task = getTask();
if (task == null) {
return false;
@@ -6227,7 +6233,7 @@
final boolean behindOccludedContainer = !task.shouldBeVisible(null /* starting */)
|| task.getOccludingActivityAbove(this) != null;
- return shouldBeVisible(behindOccludedContainer, false /* ignoringKeyguard */);
+ return shouldBeVisible(behindOccludedContainer, ignoringKeyguard);
}
void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
@@ -10509,8 +10515,7 @@
if (!getTurnScreenOnFlag()) {
return false;
}
- final Task rootTask = getRootTask();
- return mCurrentLaunchCanTurnScreenOn && rootTask != null
+ return mCurrentLaunchCanTurnScreenOn
&& mTaskSupervisor.getKeyguardController().checkKeyguardVisibility(this);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 182e1c1..2cccd33 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -523,8 +523,11 @@
void onActivityLaunched(TaskInfo taskInfo, ActivityRecord r) {
final SparseArray<ActivityInterceptorCallback> callbacks =
mService.getActivityInterceptorCallbacks();
- ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
- r::clearOptionsAnimationForSiblings);
+ final ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(() -> {
+ synchronized (mService.mGlobalLock) {
+ r.clearOptionsAnimationForSiblings();
+ }
+ });
for (int i = 0; i < callbacks.size(); i++) {
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
callback.onActivityLaunched(taskInfo, r.info, info);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a5687e6..d984fb1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -390,6 +390,9 @@
final VisibleActivityProcessTracker mVisibleActivityProcessTracker;
+ /** The starting activities which are waiting for their processes to attach. */
+ final ArrayList<ActivityRecord> mStartingProcessActivities = new ArrayList<>();
+
/* Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock = new WindowManagerGlobalLock();
/**
@@ -3779,17 +3782,25 @@
}
EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
r.shortComponentName, Boolean.toString(isAutoEnter));
- r.setPictureInPictureParams(params);
- r.mAutoEnteringPip = isAutoEnter;
- mRootWindowContainer.moveActivityToPinnedRootTask(r,
- null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
- transition);
- // Continue the pausing process after entering pip.
- if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
- r.getTask().schedulePauseActivity(r, false /* userLeaving */,
- false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+
+ // Ensure the ClientTransactionItems are bundled for this operation.
+ deferWindowLayout();
+ try {
+ r.setPictureInPictureParams(params);
+ r.mAutoEnteringPip = isAutoEnter;
+ mRootWindowContainer.moveActivityToPinnedRootTask(r,
+ null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+ transition);
+ // Continue the pausing process after entering pip.
+ if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
+ r.getTask().schedulePauseActivity(r, false /* userLeaving */,
+ false /* pauseImmediately */, true /* autoEnteringPip */,
+ "auto-pip");
+ }
+ r.mAutoEnteringPip = false;
+ } finally {
+ continueWindowLayout();
}
- r.mAutoEnteringPip = false;
}
};
@@ -4323,6 +4334,9 @@
if (mDemoteTopAppReasons != 0) {
pw.println(" mDemoteTopAppReasons=" + mDemoteTopAppReasons);
}
+ if (!mStartingProcessActivities.isEmpty()) {
+ pw.println(" mStartingProcessActivities=" + mStartingProcessActivities);
+ }
}
if (!printedAnything) {
@@ -5170,6 +5184,13 @@
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
String hostingType) {
+ if (!mStartingProcessActivities.contains(activity)) {
+ mStartingProcessActivities.add(activity);
+ } else if (mProcessNames.get(
+ activity.processName, activity.info.applicationInfo.uid) != null) {
+ // The process is already starting. Wait for it to attach.
+ return;
+ }
try {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
@@ -6166,7 +6187,20 @@
@Override
public void onProcessRemoved(String name, int uid) {
synchronized (mGlobalLockWithoutBoost) {
- mProcessNames.remove(name, uid);
+ final WindowProcessController proc = mProcessNames.remove(name, uid);
+ if (proc != null && !mStartingProcessActivities.isEmpty()) {
+ for (int i = mStartingProcessActivities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = mStartingProcessActivities.get(i);
+ if (uid == r.info.applicationInfo.uid && name.equals(r.processName)) {
+ Slog.w(TAG, proc + " is removed with pending start " + r);
+ mStartingProcessActivities.remove(i);
+ // If visible, finish it to avoid getting stuck on screen.
+ if (r.isVisibleRequested()) {
+ r.finishIfPossible("starting-proc-removed", false /* oomAdj */);
+ }
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d709fa5..d0c6e15 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -811,6 +811,13 @@
return mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
}
+ boolean shouldPauseTouch(WindowContainer wc) {
+ // Once the close transition is ready, it means the onBackInvoked callback has invoked, and
+ // app is ready to trigger next transition, no matter what it will be.
+ return mAnimationHandler.mComposed && mWaitTransitionFinish == null
+ && mAnimationHandler.isTarget(wc, wc.isVisibleRequested() /* open */);
+ }
+
/**
* Cleanup animation, this can either happen when legacy transition ready, or when the Shell
* transition finish.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 739f76e..00d42e0 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -289,6 +290,9 @@
static final float INVALID_DPI = 0.0f;
+ private final boolean mVisibleBackgroundUserEnabled =
+ UserManager.isVisibleBackgroundUsersEnabled();
+
@IntDef(prefix = { "FORCE_SCALING_MODE_" }, value = {
FORCE_SCALING_MODE_AUTO,
FORCE_SCALING_MODE_DISABLED
@@ -1999,7 +2003,12 @@
}
// Update directly because the app which will change the orientation of display is ready.
if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
- sendNewConfiguration();
+ // Run rotation change on display thread. See Transition#shouldApplyOnDisplayThread().
+ mWmService.mH.post(() -> {
+ synchronized (mWmService.mGlobalLock) {
+ sendNewConfiguration();
+ }
+ });
return;
}
if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
@@ -2698,11 +2707,15 @@
* Returns true if the specified UID has access to this display.
*/
boolean hasAccess(int uid) {
- int userId = UserHandle.getUserId(uid);
- boolean isUserVisibleOnDisplay = mWmService.mUmInternal.isUserVisible(
- userId, mDisplayId);
- return mDisplay.hasAccess(uid)
- && (userId == UserHandle.USER_SYSTEM || isUserVisibleOnDisplay);
+ if (!mDisplay.hasAccess(uid)) {
+ return false;
+ }
+ if (!mVisibleBackgroundUserEnabled) {
+ return true;
+ }
+ final int userId = UserHandle.getUserId(uid);
+ return userId == UserHandle.USER_SYSTEM
+ || mWmService.mUmInternal.isUserVisible(userId, mDisplayId);
}
boolean isPrivate() {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 45cf10b..5aa0ed7 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -327,14 +327,14 @@
R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
- mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxHorizontalPositionMultiplier);
- mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxVerticalPositionMultiplier);
- mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxBookModePositionMultiplier);
- mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
- R.dimen.config_letterboxTabletopModePositionMultiplier);
+ setLetterboxHorizontalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxHorizontalPositionMultiplier));
+ setLetterboxVerticalPositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxVerticalPositionMultiplier));
+ setLetterboxBookModePositionMultiplier(mContext.getResources().getFloat(
+ R.dimen.config_letterboxBookModePositionMultiplier));
+ setLetterboxTabletopModePositionMultiplier(mContext.getResources()
+ .getFloat(R.dimen.config_letterboxTabletopModePositionMultiplier));
mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsHorizontalReachabilityEnabled);
mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
@@ -657,29 +657,8 @@
* right side.
*/
float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
- if (isInBookMode) {
- if (mLetterboxBookModePositionMultiplier < 0.0f
- || mLetterboxBookModePositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
- + mLetterboxBookModePositionMultiplier);
- // Default to left position if invalid value is provided.
- return 0.0f;
- } else {
- return mLetterboxBookModePositionMultiplier;
- }
- } else {
- if (mLetterboxHorizontalPositionMultiplier < 0.0f
- || mLetterboxHorizontalPositionMultiplier > 1.0f) {
- Slog.w(TAG,
- "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
- + mLetterboxBookModePositionMultiplier);
- // Default to central position if invalid value is provided.
- return 0.5f;
- } else {
- return mLetterboxHorizontalPositionMultiplier;
- }
- }
+ return isInBookMode ? mLetterboxBookModePositionMultiplier
+ : mLetterboxHorizontalPositionMultiplier;
}
/*
@@ -689,37 +668,28 @@
* bottom side.
*/
float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
- if (isInTabletopMode) {
- return (mLetterboxTabletopModePositionMultiplier < 0.0f
- || mLetterboxTabletopModePositionMultiplier > 1.0f)
- // Default to top position if invalid value is provided.
- ? 0.0f : mLetterboxTabletopModePositionMultiplier;
- } else {
- return (mLetterboxVerticalPositionMultiplier < 0.0f
- || mLetterboxVerticalPositionMultiplier > 1.0f)
- // Default to central position if invalid value is provided.
- ? 0.5f : mLetterboxVerticalPositionMultiplier;
- }
+ return isInTabletopMode ? mLetterboxTabletopModePositionMultiplier
+ : mLetterboxVerticalPositionMultiplier;
}
/**
- * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides horizontal position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxHorizontalPositionMultiplier(float multiplier) {
- mLetterboxHorizontalPositionMultiplier = multiplier;
+ mLetterboxHorizontalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxHorizontalPositionMultiplier");
}
/**
- * Overrides vertical position of a center of the letterboxed app window. If given value < 0
- * or > 1, then it and a value of {@link
- * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} are ignored and
- * central position (0.5) is used.
+ * Overrides vertical position of a center of the letterboxed app window.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
*/
void setLetterboxVerticalPositionMultiplier(float multiplier) {
- mLetterboxVerticalPositionMultiplier = multiplier;
+ mLetterboxVerticalPositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxVerticalPositionMultiplier");
}
/**
@@ -740,6 +710,28 @@
com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier);
}
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxTabletopModePositionMultiplier(float multiplier) {
+ mLetterboxTabletopModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxTabletopModePositionMultiplier");
+ }
+
+ /**
+ * Sets tabletop mode position multiplier.
+ *
+ * @throws IllegalArgumentException If given value < 0 or > 1.
+ */
+ @VisibleForTesting
+ void setLetterboxBookModePositionMultiplier(float multiplier) {
+ mLetterboxBookModePositionMultiplier = assertValidMultiplier(multiplier,
+ "mLetterboxBookModePositionMultiplier");
+ }
+
/*
* Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
* landscape device orientation.
@@ -1356,4 +1348,21 @@
void resetUserAppAspectRatioFullscreenEnabled() {
setUserAppAspectRatioFullscreenOverrideEnabled(false);
}
+
+ /**
+ * Checks whether the multiplier is between [0,1].
+ *
+ * @param multiplierName sent in the exception if multiplier is invalid, for easier debugging.
+ *
+ * @return multiplier, if valid
+ * @throws IllegalArgumentException if outside bounds.
+ */
+ private float assertValidMultiplier(float multiplier, String multiplierName)
+ throws IllegalArgumentException {
+ if (multiplier < 0.0f || multiplier > 1.0f) {
+ throw new IllegalArgumentException("Trying to set " + multiplierName
+ + " out of bounds: " + multiplier);
+ }
+ return multiplier;
+ }
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac..1e88fe4 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -72,7 +72,6 @@
import static com.android.server.wm.RootWindowContainerProto.WINDOW_CONTAINER;
import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
-import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_INVISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -270,8 +269,6 @@
private int mTmpTaskLayerRank;
private final RankTaskLayersRunnable mRankTaskLayersRunnable = new RankTaskLayersRunnable();
- private final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();
-
private String mDestroyAllActivitiesReason;
private final Runnable mDestroyAllActivitiesRunnable = new Runnable() {
@Override
@@ -1838,11 +1835,39 @@
}
boolean attachApplication(WindowProcessController app) throws RemoteException {
- try {
- return mAttachApplicationHelper.process(app);
- } finally {
- mAttachApplicationHelper.reset();
+ final ArrayList<ActivityRecord> activities = mService.mStartingProcessActivities;
+ RemoteException remoteException = null;
+ boolean hasActivityStarted = false;
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityRecord r = activities.get(i);
+ if (app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
+ // The attaching process does not match the starting activity.
+ continue;
+ }
+ // Consume the pending record.
+ activities.remove(i);
+ final TaskFragment tf = r.getTaskFragment();
+ if (tf == null || r.finishing || r.app != null
+ // Ignore keyguard because the app may use show-when-locked when creating.
+ || !r.shouldBeVisible(true /* ignoringKeyguard */)
+ || !r.showToCurrentUser()) {
+ continue;
+ }
+ try {
+ final boolean canResume = r.isFocusable() && r == tf.topRunningActivity();
+ if (mTaskSupervisor.realStartActivityLocked(r, app, canResume,
+ true /* checkConfig */)) {
+ hasActivityStarted = true;
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception in new process when starting " + r, e);
+ remoteException = e;
+ }
}
+ if (remoteException != null) {
+ throw remoteException;
+ }
+ return hasActivityStarted;
}
/**
@@ -2378,6 +2403,14 @@
return false;
}
+ return resumeFocusedTasksTopActivitiesUnchecked(targetRootTask, target, targetOptions,
+ deferPause);
+ }
+
+ @VisibleForTesting
+ boolean resumeFocusedTasksTopActivitiesUnchecked(
+ Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
+ boolean deferPause) {
boolean result = false;
if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
|| getTopDisplayFocusedRootTask() == targetRootTask)) {
@@ -3740,67 +3773,4 @@
}
}
}
-
- private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
- private boolean mHasActivityStarted;
- private RemoteException mRemoteException;
- private WindowProcessController mApp;
- private ActivityRecord mTop;
-
- void reset() {
- mHasActivityStarted = false;
- mRemoteException = null;
- mApp = null;
- mTop = null;
- }
-
- boolean process(WindowProcessController app) throws RemoteException {
- mApp = app;
- for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
- getChildAt(displayNdx).forAllRootTasks(this);
- if (mRemoteException != null) {
- throw mRemoteException;
- }
- }
- if (!mHasActivityStarted) {
- ensureActivitiesVisible();
- }
- return mHasActivityStarted;
- }
-
- @Override
- public void accept(Task rootTask) {
- if (mRemoteException != null) {
- return;
- }
- if (rootTask.getVisibility(null /* starting */)
- == TASK_FRAGMENT_VISIBILITY_INVISIBLE) {
- return;
- }
- mTop = rootTask.topRunningActivity();
- rootTask.forAllActivities(this);
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard
- || r.app != null || mApp.mUid != r.info.applicationInfo.uid
- || !mApp.mName.equals(r.processName)) {
- return false;
- }
-
- try {
- if (mTaskSupervisor.realStartActivityLocked(r, mApp,
- mTop == r && r.getTask().canBeResumed(r) /* andResume */,
- true /* checkConfig */)) {
- mHasActivityStarted = true;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
- mRemoteException = e;
- return true;
- }
- return false;
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ed88b5a..143605a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2446,6 +2446,9 @@
ProtoLog.i(WM_DEBUG_SCREEN_ON,
"Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,
viewVisibility, new RuntimeException().fillInStackTrace());
+ if (becameVisible) {
+ onWindowVisible(win);
+ }
win.setDisplayLayoutNeeded();
win.mGivenInsetsPending = (flags & WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
@@ -10168,7 +10171,7 @@
* Called to notify WMS that the specified window has become visible. This shows a Toast if the
* window is deemed to hold sensitive content.
*/
- void onWindowVisible(@NonNull WindowState w) {
+ private void onWindowVisible(@NonNull WindowState w) {
showToastIfBlockingScreenCapture(w);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 731184f..1f06bfa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -848,7 +848,12 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
@@ -867,7 +872,12 @@
return -1;
}
synchronized (mInternal.mGlobalLock) {
- mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ try {
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println("Error: invalid multiplier value " + e);
+ return -1;
+ }
}
return 0;
}
@@ -1539,9 +1549,9 @@
pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
pw.println(" be ignored and framework implementation will determine aspect ratio.");
pw.println(" --cornerRadius radius");
- pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
- pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
- pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" Corners radius (in pixels) for activities in the letterbox mode.");
+ pw.println(" If radius < 0, both R.integer.config_letterboxActivityCornersRadius");
+ pw.println(" and it will be ignored and corners of the activity won't be rounded.");
pw.println(" --backgroundType [reset|solid_color|app_color_background");
pw.println(" |app_color_background_floating|wallpaper]");
pw.println(" Type of background used in the letterbox mode.");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1ad1be..8c9317a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -69,6 +69,7 @@
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -829,18 +830,20 @@
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+ final boolean wasPrevFocusableAndVisible = tr.isFocusableAndVisible();
+
int effects = applyChanges(tr, c);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
tr.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -873,8 +876,17 @@
boolean canEnterPip = activity.checkEnterPictureInPictureState(
"applyTaskChanges", true /* beforeStopping */);
if (canEnterPip) {
- canEnterPip = mService.mActivityClientController
- .requestPictureInPictureMode(activity);
+ mService.mTaskSupervisor.beginDeferResume();
+ try {
+ canEnterPip = mService.mActivityClientController
+ .requestPictureInPictureMode(activity);
+ } finally {
+ mService.mTaskSupervisor.endDeferResume();
+ if (canEnterPip && !isPip2ExperimentEnabled()) {
+ // Wait until the transaction is applied to only resume once.
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
}
if (!canEnterPip) {
// Restore the flag to its previous state when the activity cannot enter PIP.
@@ -883,6 +895,11 @@
}
}
+ // Activity in this Task may resume/pause when enter/exit pip.
+ if (wasPrevFocusableAndVisible != tr.isFocusableAndVisible()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+
return effects;
}
@@ -948,7 +965,7 @@
}
if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
taskFragment.setForceTranslucent(c.getForceTranslucent());
- effects = TRANSACT_EFFECTS_LIFECYCLE;
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
effects |= applyChanges(taskFragment, c);
@@ -2293,6 +2310,9 @@
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
taskFragment.setTaskFragmentOrganizer(organizerToken,
ownerActivity.getUid(), ownerActivity.info.processName);
+ if (mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder())) {
+ taskFragment.setOverrideOrientation(creationParams.getOverrideOrientation());
+ }
final int position;
if (creationParams.getPairedPrimaryFragmentToken() != null) {
// When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4d9fc6c..2fcee50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,7 +28,6 @@
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.permission.flags.Flags.sensitiveContentImprovements;
import static android.view.SurfaceControl.Transaction;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
@@ -2139,9 +2138,6 @@
}
}
setDisplayLayoutNeeded();
- if (sensitiveContentImprovements() && visible) {
- mWmService.onWindowVisible(this);
- }
}
}
@@ -2974,6 +2970,11 @@
return true;
}
+ // Do not allow back predictive animation target to receive touch, app can trigger an
+ // unexpected transition so basically unable to polish it.
+ if (mWmService.mAtmService.mBackNavigationController.shouldPauseTouch(mActivityRecord)) {
+ return false;
+ }
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.isVisibleRequested();
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 88c47f3..2f880ba 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -128,7 +128,8 @@
jmethodID getVirtualKeyQuietTimeMillis;
jmethodID getExcludedDeviceNames;
jmethodID getInputPortAssociations;
- jmethodID getInputUniqueIdAssociations;
+ jmethodID getInputUniqueIdAssociationsByPort;
+ jmethodID getInputUniqueIdAssociationsByDescriptor;
jmethodID getDeviceTypeAssociations;
jmethodID getKeyboardLayoutAssociations;
jmethodID getHoverTapTimeout;
@@ -634,10 +635,13 @@
env->DeleteLocalRef(portAssociations);
}
- outConfig->uniqueIdAssociations =
- readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
- .getInputUniqueIdAssociations,
- "getInputUniqueIdAssociations");
+ outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+ std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
+ "getInputUniqueIdAssociationsByPort");
+
+ outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
+ std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
+ "getInputUniqueIdAssociationsByDescriptor");
outConfig->deviceTypeAssociations =
readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo
@@ -3090,8 +3094,11 @@
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;");
GET_METHOD_ID(gServiceClassInfo.getDeviceTypeAssociations, clazz, "getDeviceTypeAssociations",
"()[Ljava/lang/String;");
diff --git a/services/core/jni/linux/usb/f_accessory.h b/services/core/jni/linux/usb/f_accessory.h
new file mode 100644
index 0000000..abd864c
--- /dev/null
+++ b/services/core/jni/linux/usb/f_accessory.h
@@ -0,0 +1,34 @@
+/*
+ * This file is auto-generated. Modifications will be lost.
+ *
+ * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
+ * for more information.
+ */
+#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H
+#define _UAPI_LINUX_USB_F_ACCESSORY_H
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+#define ACCESSORY_GET_PROTOCOL 51
+#define ACCESSORY_SEND_STRING 52
+#define ACCESSORY_START 53
+#define ACCESSORY_REGISTER_HID 54
+#define ACCESSORY_UNREGISTER_HID 55
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+#define ACCESSORY_SEND_HID_EVENT 57
+#define ACCESSORY_SET_AUDIO_MODE 58
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+#endif
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index bb46c44..73e53e2 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -28,7 +28,6 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -151,7 +150,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
// Not needed since UI is not involved
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 3513cb5..9781fb9 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -31,7 +31,6 @@
import android.credentials.selection.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -164,7 +163,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
String exception = CreateCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index bfa2d61..e73dacb 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -15,8 +15,6 @@
*/
package com.android.server.credentials;
-import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
-
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -82,25 +80,18 @@
UserSelectionDialogResult selection = UserSelectionDialogResult
.fromResultData(resultData);
if (selection != null) {
- ResultReceiver resultReceiver = resultData.getParcelable(
- EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class);
- mCallbacks.onUiSelection(selection, resultReceiver);
+ mCallbacks.onUiSelection(selection);
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ true,
- resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class));
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
mStatus = UiStatus.TERMINATED;
- mCallbacks.onUiCancellation(/* isUserCancellation= */ false,
- resultData.getParcelable(EXTRA_FINAL_RESPONSE_RECEIVER,
- ResultReceiver.class));
+ mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
break;
case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE:
mStatus = UiStatus.TERMINATED;
@@ -124,10 +115,10 @@
*/
public interface CredentialManagerUiCallback {
/** Called when the user makes a selection. */
- void onUiSelection(UserSelectionDialogResult selection, ResultReceiver resultReceiver);
+ void onUiSelection(UserSelectionDialogResult selection);
/** Called when the UI is canceled without a successful provider result. */
- void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver);
+ void onUiCancellation(boolean isUserCancellation);
/** Called when the selector UI fails to come up (mostly due to parsing issue today). */
void onUiSelectorInvocationFailure();
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 69d32a0..b1673e2 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -26,6 +26,7 @@
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
+import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCandidateCredentialsCallback;
@@ -64,6 +65,8 @@
private final int mAutofillSessionId;
private final int mAutofillRequestId;
+ private final ResultReceiver mAutofillCallback;
+
public GetCandidateRequestSession(
Context context, SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
@@ -77,6 +80,8 @@
mClientBinder = clientBinder;
mAutofillSessionId = request.getData().getInt(SESSION_ID_KEY, -1);
mAutofillRequestId = request.getData().getInt(REQUEST_ID_KEY, -1);
+ mAutofillCallback = request.getData().getParcelable(
+ CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, ResultReceiver.class);
if (mClientBinder != null) {
setUpClientCallbackListener(mClientBinder);
}
@@ -155,34 +160,34 @@
public void onFinalErrorReceived(ComponentName componentName, String errorType,
String message) {
Slog.d(TAG, "onFinalErrorReceived");
- respondToFinalReceiverWithFailureAndFinish(this.mFinalResponseReceiver, errorType, message);
+ if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
+ Slog.d(TAG, "User canceled but session is not being terminated");
+ return;
+ }
+ respondToFinalReceiverWithFailureAndFinish(errorType, message);
}
@Override
- public void onUiCancellation(boolean isUserCancellation,
- @Nullable ResultReceiver finalResponseReceiver) {
- String exception = GetCandidateCredentialsException.TYPE_USER_CANCELED;
- String message = "User cancelled the selector";
- if (!isUserCancellation) {
- exception = GetCandidateCredentialsException.TYPE_INTERRUPTED;
- message = "The UI was interrupted - please try again.";
- }
- mRequestSessionMetric.collectFrameworkException(exception);
- respondToFinalReceiverWithFailureAndFinish(finalResponseReceiver, exception, message);
+ public void onUiCancellation(boolean isUserCancellation) {
+ Slog.d(TAG, "User canceled but session is not being terminated");
}
private void respondToFinalReceiverWithFailureAndFinish(
- ResultReceiver finalResponseReceiver,
String exception, String message
) {
- if (finalResponseReceiver != null) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+
+ if (mAutofillCallback != null) {
Bundle resultData = new Bundle();
resultData.putStringArray(
CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
new String[] {exception, message});
- finalResponseReceiver.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
+ mAutofillCallback.send(Constants.FAILURE_CREDMAN_SELECTOR, resultData);
} else {
- Slog.w(TAG, "onUiCancellation called but finalResponseReceiver not found");
+ Slog.w(TAG, "onUiCancellation called but mAutofillCallback not found");
}
finishSession(/*propagateCancellation=*/false, ApiStatus.FAILURE.getMetricCode());
}
@@ -219,12 +224,25 @@
public void onFinalResponseReceived(ComponentName componentName,
GetCredentialResponse response) {
Slog.d(TAG, "onFinalResponseReceived");
- if (this.mFinalResponseReceiver != null) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+ respondToFinalReceiverWithResponseAndFinish(response);
+ }
+
+ private void respondToFinalReceiverWithResponseAndFinish(GetCredentialResponse response) {
+ if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
+ Slog.w(TAG, "Request has already been completed. This is strange.");
+ return;
+ }
+
+ if (this.mAutofillCallback != null) {
Slog.d(TAG, "onFinalResponseReceived sending through final receiver");
Bundle resultData = new Bundle();
resultData.putParcelable(
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
- mFinalResponseReceiver.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
+ mAutofillCallback.send(Constants.SUCCESS_CREDMAN_SELECTOR, resultData);
finishSession(/*propagateCancellation=*/ false, ApiStatus.SUCCESS.getMetricCode());
} else {
Slog.w(TAG, "onFinalResponseReceived result receiver not found for pinned entry");
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c26229b..be36b6c 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -32,7 +32,6 @@
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
import android.util.Slog;
@@ -166,7 +165,7 @@
}
@Override
- public void onUiCancellation(boolean isUserCancellation, ResultReceiver resultReceiver) {
+ public void onUiCancellation(boolean isUserCancellation) {
String exception = GetCredentialException.TYPE_USER_CANCELED;
String message = "User cancelled the selector";
if (!isUserCancellation) {
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 054ba2b..c0bc8e0 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -17,7 +17,6 @@
package com.android.server.credentials;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -35,7 +34,6 @@
import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteException;
-import android.os.ResultReceiver;
import android.os.UserHandle;
import android.service.credentials.CallingAppInfo;
import android.util.Slog;
@@ -104,9 +102,6 @@
protected PendingIntent mPendingIntent;
- @Nullable
- protected ResultReceiver mFinalResponseReceiver;
-
@NonNull
protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
@@ -225,8 +220,7 @@
// UI callbacks
@Override // from CredentialManagerUiCallbacks
- public void onUiSelection(UserSelectionDialogResult selection,
- ResultReceiver finalResponseReceiver) {
+ public void onUiSelection(UserSelectionDialogResult selection) {
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
Slog.w(TAG, "Request has already been completed. This is strange.");
return;
@@ -242,7 +236,7 @@
Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
return;
}
- mFinalResponseReceiver = finalResponseReceiver;
+
ProviderSessionMetric providerSessionMetric = providerSession.mProviderSessionMetric;
int initialAuthMetricsProvider = providerSessionMetric.getBrowsedAuthenticationMetric()
.size();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index 94c1374..d1830126 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -215,6 +215,9 @@
} catch (PackageManager.NameNotFoundException e) {
return false;
}
+ if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
+ return false;
+ }
final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction);
return packageInfo.applicationInfo.metaData.getBoolean(metadataKey);
}
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 b0eee08..1666fef 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -1708,7 +1708,6 @@
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
*/
@Test
- @FlakyTest(bugId = 127687569)
public void testCreateVirtualDisplay_setSurface() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 783971a..89b48ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,9 +16,18 @@
package com.android.server.am;
+import static android.os.Process.INVALID_UID;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_NULL;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_ONE_SHOT_SENT;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANCELED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
+import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -34,6 +43,7 @@
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.os.Looper;
+import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -54,6 +64,7 @@
private static final String TEST_PACKAGE_NAME = "test-package-1";
private static final String TEST_FEATURE_ID = "test-feature-1";
private static final int TEST_CALLING_UID = android.os.Process.myUid();
+ private static final int TEST_USER_ID = 0;
private static final Intent[] TEST_INTENTS = new Intent[]{new Intent("com.test.intent")};
@Mock
@@ -92,7 +103,7 @@
private PendingIntentRecord createPendingIntentRecord(int flags) {
return mPendingIntentController.getIntentSender(ActivityManager.INTENT_SENDER_BROADCAST,
- TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, 0, null, null, 0,
+ TEST_PACKAGE_NAME, TEST_FEATURE_ID, TEST_CALLING_UID, TEST_USER_ID, null, null, 0,
TEST_INTENTS, null, flags, null);
}
@@ -126,6 +137,54 @@
piCaptor.getValue().getTarget());
}
+ @Test
+ public void testCancellationReason() {
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ assertCancelReason(CANCEL_REASON_NULL, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.cancelIntentSender(pir);
+ assertCancelReason(CANCEL_REASON_OWNER_CANCELED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ createPendingIntentRecord(PendingIntent.FLAG_CANCEL_CURRENT);
+ assertCancelReason(CANCEL_REASON_SUPERSEDED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(PendingIntent.FLAG_ONE_SHOT);
+ pir.send(0, null, null, null, null, null, null);
+ assertCancelReason(CANCEL_REASON_ONE_SHOT_SENT, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(TEST_PACKAGE_NAME,
+ TEST_USER_ID, UserHandle.getAppId(TEST_CALLING_UID), true,
+ CANCEL_REASON_OWNER_FORCE_STOPPED);
+ assertCancelReason(CANCEL_REASON_OWNER_FORCE_STOPPED, pir.cancelReason);
+ }
+
+ {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ mPendingIntentController.removePendingIntentsForPackage(null,
+ TEST_USER_ID, INVALID_UID, true,
+ CANCEL_REASON_USER_STOPPED);
+ assertCancelReason(CANCEL_REASON_USER_STOPPED, pir.cancelReason);
+ }
+ }
+
+ private void assertCancelReason(int expectedReason, int actualReason) {
+ final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+ + "; Actual: " + cancelReasonToString(actualReason);
+ assertEquals(errMsg, expectedReason, actualReason);
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
index ad29392..d51828e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java
@@ -67,12 +67,11 @@
private static final int UID_2 = 99;
private final MockClock mMockClock = new MockClock();
private final HandlerThread mHandlerThread = new HandlerThread("test");
+ private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
private Handler mHandler;
private PowerStats mCollectedStats;
private PowerProfile mPowerProfile = new PowerProfile();
@Mock
- private PowerStatsUidResolver mUidResolver;
- @Mock
private CpuPowerStatsCollector.KernelCpuStatsReader mMockKernelCpuStatsReader;
@Mock
private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
@@ -144,15 +143,8 @@
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
when(mMockKernelCpuStatsReader.isSupportedFeature()).thenReturn(true);
- when(mUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
- int uid = invocation.getArgument(0);
- if (uid == ISOLATED_UID) {
- return UID_2;
- } else {
- return uid;
- }
- });
when(mConsumedEnergyRetriever.getEnergyConsumerIds(anyInt())).thenReturn(new int[0]);
+ mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
}
@Test
@@ -268,8 +260,7 @@
mockEnergyConsumers();
CpuPowerStatsCollector collector = createCollector(8, 0);
- CpuPowerStatsLayout layout =
- new CpuPowerStatsLayout();
+ CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
layout.fromExtras(collector.getPowerStatsDescriptor().extras);
mockKernelCpuStats(new long[]{1111, 2222, 3333},
@@ -333,6 +324,45 @@
.isEqualTo(78);
}
+ @Test
+ public void isolatedUidReuse() {
+ mockCpuScalingPolicies(1);
+ mockPowerProfile();
+ mockEnergyConsumers();
+
+ CpuPowerStatsCollector collector = createCollector(8, 0);
+ CpuPowerStatsLayout layout = new CpuPowerStatsLayout();
+ layout.fromExtras(collector.getPowerStatsDescriptor().extras);
+
+ mockKernelCpuStats(new long[]{1111, 2222, 3333},
+ new SparseArray<>() {{
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{10000, 20000});
+ }}, 0, 1234);
+
+ mMockClock.uptime = 1000;
+ collector.forceSchedule();
+ waitForIdle();
+
+ mUidResolver.noteIsolatedUidRemoved(ISOLATED_UID, UID_2);
+ mUidResolver.noteIsolatedUidAdded(ISOLATED_UID, UID_2);
+
+ mockKernelCpuStats(new long[]{5555, 4444, 3333},
+ new SparseArray<>() {{
+ put(UID_2, new long[]{100, 150});
+ put(ISOLATED_UID, new long[]{245, 528});
+ }}, 1234, 3421);
+
+ mMockClock.uptime = 2000;
+ collector.forceSchedule();
+ waitForIdle();
+
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 0))
+ .isEqualTo(245);
+ assertThat(layout.getUidTimeByPowerBracket(mCollectedStats.uidStats.get(UID_2), 1))
+ .isEqualTo(528);
+ }
+
private void mockCpuScalingPolicies(int clusterCount) {
SparseArray<int[]> cpus = new SparseArray<>();
SparseArray<int[]> freqs = new SparseArray<>();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index df1200b..89d6c1c 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -66,8 +66,7 @@
public void setup() {
mHandlerThread.start();
mHandler = mHandlerThread.getThreadHandler();
- mCollector = new PowerStatsCollector(mHandler,
- 60000,
+ mCollector = new PowerStatsCollector(mHandler, 60000, mock(PowerStatsUidResolver.class),
mMockClock) {
@Override
protected PowerStats collectStats() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 7e04277..90b131a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,10 +16,10 @@
package com.android.server.biometrics;
-import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
@@ -65,8 +65,6 @@
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -204,43 +202,7 @@
}
@Test
- @RequiresFlagsDisabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
- public void testRegisterAuthenticator_registerAuthenticators() throws Exception {
- final int fingerprintId = 0;
- final int fingerprintStrength = 15;
-
- final int faceId = 1;
- final int faceStrength = 15;
-
- final String[] config = {
- // ID0:Fingerprint:Strong
- String.format("%d:2:%d", fingerprintId, fingerprintStrength),
- // ID2:Face:Convenience
- String.format("%d:8:%d", faceId, faceStrength)
- };
-
- when(mInjector.getConfiguration(any())).thenReturn(config);
-
- mAuthService = new AuthService(mContext, mInjector);
- mAuthService.onStart();
-
- verify(mFingerprintService).registerAuthenticators(mFingerprintPropsCaptor.capture());
- final FingerprintSensorPropertiesInternal fingerprintProp =
- mFingerprintPropsCaptor.getValue().get(0);
- assertEquals(fingerprintProp.sensorId, fingerprintId);
- assertEquals(fingerprintProp.sensorStrength,
- Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
-
- verify(mFaceService).registerAuthenticators(mFacePropsCaptor.capture());
- final FaceSensorPropertiesInternal faceProp = mFacePropsCaptor.getValue().get(0);
- assertEquals(faceProp.sensorId, faceId);
- assertEquals(faceProp.sensorStrength,
- Utils.authenticatorStrengthToPropertyStrength(faceStrength));
- }
-
- @Test
- @RequiresFlagsEnabled(com.android.server.biometrics.Flags.FLAG_DE_HIDL)
- public void testRegisterAuthenticator_registerAuthenticatorsLegacy() throws RemoteException {
+ public void testRegisterAuthenticator_registerAuthenticators() throws RemoteException {
final int fingerprintId = 0;
final int fingerprintStrength = 15;
@@ -265,7 +227,7 @@
mFingerprintLooper.dispatchAll();
mFaceLooper.dispatchAll();
- verify(mFingerprintService).registerAuthenticatorsLegacy(
+ verify(mFingerprintService).registerAuthenticators(
mFingerprintSensorConfigurationsCaptor.capture());
final SensorProps[] fingerprintProp = mFingerprintSensorConfigurationsCaptor.getValue()
@@ -275,8 +237,7 @@
assertEquals(fingerprintProp[0].commonProps.sensorStrength,
Utils.authenticatorStrengthToPropertyStrength(fingerprintStrength));
- verify(mFaceService).registerAuthenticatorsLegacy(
- mFaceSensorConfigurationsCaptor.capture());
+ verify(mFaceService).registerAuthenticators(mFaceSensorConfigurationsCaptor.capture());
final android.hardware.biometrics.face.SensorProps[] faceProp =
mFaceSensorConfigurationsCaptor.getValue()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 5fd29c2..503ab8e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -76,7 +76,6 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
@@ -90,7 +89,6 @@
import android.view.DisplayInfo;
import android.view.WindowManager;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -246,14 +244,8 @@
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
-
- if (com.android.server.biometrics.Flags.deHidl()) {
- when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
- new Handler(TestableLooper.get(this).getLooper()));
- } else {
- when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
- new Handler(Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
+ new Handler(TestableLooper.get(this).getLooper()));
final String[] config = {
"0:2:15", // ID0:Fingerprint:Strong
@@ -2037,11 +2029,7 @@
}
private void waitForIdle() {
- if (com.android.server.biometrics.Flags.deHidl()) {
- TestableLooper.get(this).processAllMessages();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ TestableLooper.get(this).processAllMessages();
}
private byte[] generateRandomHAT() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
index c7300bb..960357f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricContextProviderTest.java
@@ -87,8 +87,12 @@
private ISessionListener mSessionListener;
@Mock
private WindowManager mWindowManager;
+ @Mock
+ private Consumer<OperationContext> mStartHalConsumer;
- private OperationContextExt mOpContext = new OperationContextExt(true);
+ private final FingerprintAuthenticateOptions mAuthenticateOptions =
+ new FingerprintAuthenticateOptions.Builder().build();
+ private final OperationContextExt mOpContext = new OperationContextExt(true);
private IBiometricContextListener mListener;
private BiometricContextProvider mProvider;
@@ -200,11 +204,11 @@
final List<Integer> actual = new ArrayList<>();
final List<Integer> expected = List.of(FoldState.FULLY_CLOSED, FoldState.FULLY_OPENED,
FoldState.UNKNOWN, FoldState.HALF_OPENED);
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.getFoldState()).isEqualTo(ctx.foldState);
actual.add(ctx.foldState);
- });
+ }, mAuthenticateOptions);
for (int v : expected) {
mListener.onFoldChanged(v);
@@ -228,11 +232,11 @@
AuthenticateOptions.DISPLAY_STATE_NO_UI,
AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.getDisplayState()).isEqualTo(ctx.displayState);
actual.add(ctx.displayState);
- });
+ }, mAuthenticateOptions);
for (int v : expected) {
mListener.onDisplayStateChanged(v);
@@ -250,11 +254,11 @@
public void testSubscribesToAod() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
assertThat(mProvider.isAod()).isEqualTo(ctx.isAod);
actual.add(ctx.isAod);
- });
+ }, mAuthenticateOptions);
for (int v : List.of(
AuthenticateOptions.DISPLAY_STATE_AOD,
@@ -273,10 +277,10 @@
public void testSubscribesToAwake() throws RemoteException {
final List<Boolean> actual = new ArrayList<>();
- mProvider.subscribe(mOpContext, ctx -> {
+ mProvider.subscribe(mOpContext, mStartHalConsumer, ctx -> {
assertThat(ctx).isSameInstanceAs(mOpContext.toAidlContext());
actual.add(mProvider.isAwake());
- });
+ }, mAuthenticateOptions);
for (int v : List.of(
AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN,
@@ -295,14 +299,15 @@
public void testSubscribesWithDifferentState() throws RemoteException {
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
- mProvider.subscribe(mOpContext, nonEmptyConsumer);
- verify(nonEmptyConsumer).accept(same(mOpContext.toAidlContext()));
+ mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
+
+ assertThat(mOpContext.getDisplayState()).isEqualTo(AuthenticateOptions.DISPLAY_STATE_AOD);
}
@Test
public void testUnsubscribes() throws RemoteException {
final Consumer<OperationContext> emptyConsumer = mock(Consumer.class);
- mProvider.subscribe(mOpContext, emptyConsumer);
+ mProvider.subscribe(mOpContext, mStartHalConsumer, emptyConsumer, mAuthenticateOptions);
mProvider.unsubscribe(mOpContext);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_AOD);
@@ -311,7 +316,7 @@
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_UNKNOWN);
final Consumer<OperationContext> nonEmptyConsumer = mock(Consumer.class);
- mProvider.subscribe(mOpContext, nonEmptyConsumer);
+ mProvider.subscribe(mOpContext, mStartHalConsumer, nonEmptyConsumer, mAuthenticateOptions);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_LOCKSCREEN);
mProvider.unsubscribe(mOpContext);
mListener.onDisplayStateChanged(AuthenticateOptions.DISPLAY_STATE_NO_UI);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 971323a..fc573d2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -52,13 +52,12 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
-import android.testing.TestableLooper;
import android.util.Slog;
import androidx.annotation.NonNull;
@@ -66,7 +65,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.nano.BiometricSchedulerProto;
@@ -79,7 +77,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.HashMap;
@@ -90,7 +89,6 @@
@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BiometricSchedulerTest {
private static final String TAG = "BiometricSchedulerTest";
@@ -105,6 +103,9 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
private BiometricScheduler<IFingerprint, ISession> mScheduler;
private IBinder mToken;
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@@ -121,8 +122,10 @@
mUsersStoppedCount++;
mCurrentUserId = UserHandle.USER_NULL;
};
+ private TestLooper mLooper;
private boolean mStartOperationsFinish = true;
private int mStartUserClientCount = 0;
+
@Mock
private IBiometricService mBiometricService;
@Mock
@@ -140,44 +143,39 @@
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
mToken = new Binder();
+ mLooper = new TestLooper();
+
when(mAuthSessionCoordinator.getLockoutStateFor(anyInt(), anyInt())).thenReturn(
BIOMETRIC_SUCCESS);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(TestableLooper.get(this).getLooper()),
- BiometricScheduler.SENSOR_TYPE_UNKNOWN,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- LOG_NUM_RECENT_OPERATIONS,
- () -> mCurrentUserId,
- new UserSwitchProvider<IFingerprint, ISession>() {
- @NonNull
- @Override
- public StopUserClient<ISession> getStopUserClient(int userId) {
- return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStoppedCallback, () -> mShouldFailStopUser);
- }
- @NonNull
- @Override
- public StartUserClient<IFingerprint, ISession> getStartUserClient(
- int newUserId) {
- mStartUserClientCount++;
- return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
- newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStartedCallback, mStartOperationsFinish);
- }
- });
- } else {
- mScheduler = new BiometricScheduler<>(
- new Handler(TestableLooper.get(this).getLooper()),
- BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
- mBiometricService, LOG_NUM_RECENT_OPERATIONS);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_UNKNOWN,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ LOG_NUM_RECENT_OPERATIONS,
+ () -> mCurrentUserId,
+ new UserSwitchProvider<IFingerprint, ISession>() {
+ @NonNull
+ @Override
+ public StopUserClient<ISession> getStopUserClient(int userId) {
+ return new TestStopUserClient(mContext, () -> mSession, mToken, userId,
+ TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStoppedCallback, () -> mShouldFailStopUser);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<IFingerprint, ISession> getStartUserClient(
+ int newUserId) {
+ mStartUserClientCount++;
+ return new TestStartUserClient(mContext, () -> mFingerprint, mToken,
+ newUserId, TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
+ mUserStartedCallback, mStartOperationsFinish);
+ }
+ });
}
@Test
@@ -657,7 +655,6 @@
@Test
public void testClearBiometricQueue_clearsHungAuthOperation() {
// Creating a hung client
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -676,9 +673,9 @@
assertNotNull(mScheduler.mCurrentOperation);
assertEquals(0, mScheduler.getCurrentPendingCount());
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
// The hung client did not honor this operation, verify onError and authenticated
@@ -693,7 +690,6 @@
@Test
public void testAuthWorks_afterClearBiometricQueue() {
// Creating a hung client
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -714,10 +710,10 @@
waitForIdle();
// The watchdog should kick off the cancellation
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
// The hung client did not honor this operation, verify onError and authenticated
@@ -752,10 +748,10 @@
client2.getCallback().onClientFinished(client2, true);
waitForIdle();
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
//Asserting auth client passes
@@ -766,7 +762,6 @@
@Test
public void testClearBiometricQueue_doesNotClearOperationsWhenQueueNotStuck() {
//Creating clients
- final TestableLooper looper = TestableLooper.get(this);
final Supplier<Object> lazyDaemon1 = () -> mock(Object.class);
final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class), 0 /* cookie */,
@@ -793,10 +788,10 @@
waitForIdle();
// The watchdog should kick off the cancellation
- looper.moveTimeForward(10000);
+ mLooper.moveTimeForward(10000);
waitForIdle();
// After 10 seconds the HAL has 3 seconds to respond to a cancel
- looper.moveTimeForward(3000);
+ mLooper.moveTimeForward(3000);
waitForIdle();
//Watchdog does not clear pending operations
@@ -855,7 +850,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -871,7 +865,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser_notStarted() {
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
@@ -896,7 +889,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenNoUser_notStarted_andReset() {
mCurrentUserId = UserHandle.USER_NULL;
mStartOperationsFinish = false;
@@ -923,7 +915,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenSameUser() {
mCurrentUserId = 10;
@@ -940,7 +931,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleOperation_whenDifferentUser() {
mCurrentUserId = 10;
@@ -961,7 +951,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testStartUser_alwaysStartsNextOperation() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -990,7 +979,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testStartUser_failsClearsStopUserClient() {
mCurrentUserId = UserHandle.USER_NULL;
@@ -1033,7 +1021,7 @@
}
private void waitForIdle() {
- TestableLooper.get(this).processAllMessages();
+ mLooper.dispatchAll();
}
private static class TestAuthenticateOptions implements AuthenticateOptions {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
deleted file mode 100644
index dd5d826..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics.sensors;
-
-import static android.testing.TestableLooper.RunWithLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-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.biometrics.IBiometricService;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.UserHandle;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class UserAwareBiometricSchedulerTest {
-
- private static final String TAG = "UserAwareBiometricSchedulerTest";
- private static final int TEST_SENSOR_ID = 0;
-
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- private Handler mHandler;
- private UserAwareBiometricScheduler mScheduler;
- private final IBinder mToken = new Binder();
-
- @Mock
- private Context mContext;
- @Mock
- private IBiometricService mBiometricService;
- @Mock
- private BiometricLogger mBiometricLogger;
- @Mock
- private BiometricContext mBiometricContext;
-
- private boolean mShouldFailStopUser = false;
- private final StopUserClientShouldFail mStopUserClientShouldFail =
- () -> {
- return mShouldFailStopUser;
- };
- private final TestUserStartedCallback mUserStartedCallback = new TestUserStartedCallback();
- private final TestUserStoppedCallback mUserStoppedCallback = new TestUserStoppedCallback();
- private int mCurrentUserId = UserHandle.USER_NULL;
- private boolean mStartOperationsFinish = true;
- private int mStartUserClientCount = 0;
-
- @Before
- public void setUp() {
- mShouldFailStopUser = false;
- mHandler = new Handler(TestableLooper.get(this).getLooper());
- mScheduler = new UserAwareBiometricScheduler(TAG,
- mHandler,
- BiometricScheduler.SENSOR_TYPE_UNKNOWN,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> mCurrentUserId,
- new UserAwareBiometricScheduler.UserSwitchCallback() {
- @NonNull
- @Override
- public StopUserClient<?> getStopUserClient(int userId) {
- return new TestStopUserClient(mContext, Object::new, mToken, userId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStoppedCallback, mStopUserClientShouldFail);
- }
-
- @NonNull
- @Override
- public StartUserClient<?, ?> getStartUserClient(int newUserId) {
- mStartUserClientCount++;
- return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
- TEST_SENSOR_ID, mBiometricLogger, mBiometricContext,
- mUserStartedCallback, mStartOperationsFinish);
- }
- });
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenNoUser() {
- mCurrentUserId = UserHandle.USER_NULL;
-
- final BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(0);
-
- mScheduler.scheduleClientMonitor(nextClient);
- waitForIdle();
-
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(0);
- verify(nextClient).start(any());
- }
-
- @Test
- public void testScheduleOperation_whenNoUser_notStarted() {
- mCurrentUserId = UserHandle.USER_NULL;
- mStartOperationsFinish = false;
-
- final BaseClientMonitor[] nextClients = new BaseClientMonitor[]{
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class),
- mock(BaseClientMonitor.class)
- };
- for (BaseClientMonitor client : nextClients) {
- when(client.getTargetUserId()).thenReturn(5);
- mScheduler.scheduleClientMonitor(client);
- waitForIdle();
- }
-
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
- assertThat(mStartUserClientCount).isEqualTo(1);
- for (BaseClientMonitor client : nextClients) {
- verify(client, never()).start(any());
- }
- }
-
- @Test
- public void testScheduleOperation_whenNoUser_notStarted_andReset() {
- mCurrentUserId = UserHandle.USER_NULL;
- mStartOperationsFinish = false;
-
- final BaseClientMonitor client = mock(BaseClientMonitor.class);
- when(client.getTargetUserId()).thenReturn(5);
- mScheduler.scheduleClientMonitor(client);
- waitForIdle();
-
- final TestStartUserClient startUserClient =
- (TestStartUserClient) mScheduler.mCurrentOperation.getClientMonitor();
- mScheduler.reset();
- assertThat(mScheduler.mCurrentOperation).isNull();
-
- final BiometricSchedulerOperation fakeOperation = new BiometricSchedulerOperation(
- mock(BaseClientMonitor.class), new ClientMonitorCallback() {});
- mScheduler.mCurrentOperation = fakeOperation;
- startUserClient.mCallback.onClientFinished(startUserClient, true);
- assertThat(fakeOperation).isSameInstanceAs(mScheduler.mCurrentOperation);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenSameUser() {
- mCurrentUserId = 10;
-
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(mCurrentUserId);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
-
- verify(nextClient).start(any());
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mUserStartedCallback.mStartedUsers).isEmpty();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testScheduleOperation_whenDifferentUser() {
- mCurrentUserId = 10;
-
- final int nextUserId = 11;
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(nextUserId);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
-
- waitForIdle();
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(nextUserId);
-
- waitForIdle();
- verify(nextClient).start(any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testStartUser_alwaysStartsNextOperation() {
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(10);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
-
- // finish first operation
- mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
- waitForIdle();
-
- // schedule second operation but swap out the current operation
- // before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(11);
- mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(1);
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void testStartUser_failsClearsStopUserClient() {
- // When a stop user client fails, check that mStopUserClient
- // is set to null to prevent the scheduler from getting stuck.
- BaseClientMonitor nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(10);
-
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- verify(nextClient).start(any());
-
- // finish first operation
- mScheduler.getInternalCallback().onClientFinished(nextClient, true /* success */);
- waitForIdle();
-
- // schedule second operation but swap out the current operation
- // before it runs so that it's not current when it's completion callback runs
- nextClient = mock(BaseClientMonitor.class);
- when(nextClient.getTargetUserId()).thenReturn(11);
- mUserStartedCallback.mAfterStart = () -> mScheduler.mCurrentOperation = null;
- mShouldFailStopUser = true;
- mScheduler.scheduleClientMonitor(nextClient);
-
- waitForIdle();
- assertThat(mUserStartedCallback.mStartedUsers).containsExactly(10, 11).inOrder();
- assertThat(mUserStoppedCallback.mNumInvocations).isEqualTo(0);
- assertThat(mScheduler.getStopUserClient()).isEqualTo(null);
- }
-
- private void waitForIdle() {
- TestableLooper.get(this).processAllMessages();
- }
-
- private class TestUserStoppedCallback implements StopUserClient.UserStoppedCallback {
- int mNumInvocations;
-
- @Override
- public void onUserStopped() {
- mNumInvocations++;
- mCurrentUserId = UserHandle.USER_NULL;
- }
- }
-
- private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
- final List<Integer> mStartedUsers = new ArrayList<>();
- Runnable mAfterStart = null;
-
- @Override
- public void onUserStarted(int newUserId, Object newObject, int halInterfaceVersion) {
- mStartedUsers.add(newUserId);
- mCurrentUserId = newUserId;
- if (mAfterStart != null) {
- mAfterStart.run();
- }
- }
- }
-
- private interface StopUserClientShouldFail {
- boolean shouldFail();
- }
-
- private class TestStopUserClient extends StopUserClient<Object> {
- private StopUserClientShouldFail mShouldFailClient;
- public TestStopUserClient(@NonNull Context context,
- @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull UserStoppedCallback callback, StopUserClientShouldFail shouldFail) {
- super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
- mShouldFailClient = shouldFail;
- }
-
- @Override
- protected void startHalOperation() {
-
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
- if (mShouldFailClient.shouldFail()) {
- getCallback().onClientFinished(this, false /* success */);
- // When the above fails, it means that the HAL has died, in this case we
- // need to ensure the UserSwitchCallback correctly returns the NULL user handle.
- mCurrentUserId = UserHandle.USER_NULL;
- } else {
- onUserStopped();
- }
- }
-
- @Override
- public void unableToStart() {
-
- }
- }
-
- private static class TestStartUserClient extends StartUserClient<Object, Object> {
- private final boolean mShouldFinish;
-
- ClientMonitorCallback mCallback;
-
- public TestStartUserClient(@NonNull Context context,
- @NonNull Supplier<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull BiometricLogger logger,
- @NonNull BiometricContext biometricContext,
- @NonNull UserStartedCallback<Object> callback, boolean shouldFinish) {
- super(context, lazyDaemon, token, userId, sensorId, logger, biometricContext, callback);
- mShouldFinish = shouldFinish;
- }
-
- @Override
- protected void startHalOperation() {
-
- }
-
- @Override
- public void start(@NonNull ClientMonitorCallback callback) {
- super.start(callback);
-
- mCallback = callback;
- if (mShouldFinish) {
- mUserStartedCallback.onUserStarted(
- getTargetUserId(), new Object(), 1 /* halInterfaceVersion */);
- callback.onClientFinished(this, true /* success */);
- }
- }
-
- @Override
- public void unableToStart() {
-
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index e4c56a7..1ca36a3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -147,11 +147,10 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
+ public void registerAuthenticators_defaultOnly() throws Exception {
initService();
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT),
@@ -161,13 +160,13 @@
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+ @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
initService();
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -176,13 +175,13 @@
}
@Test
- @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
- public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+ @RequiresFlagsEnabled(Flags.FLAG_FACE_VHAL_FEATURE)
+ public void registerAuthenticators_virtualFaceOnly() throws Exception {
initService();
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -191,13 +190,12 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
+ public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
mFaceSensorConfigurations.addAidlConfigs(new String[]{NAME_VIRTUAL});
initService();
- mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
@@ -210,7 +208,7 @@
FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
.build();
initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
final long operationId = 5;
@@ -230,7 +228,7 @@
R.string.config_keyguardComponent,
OP_PACKAGE_NAME);
initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
+ mFaceService.mServiceWrapper.registerAuthenticators(mFaceSensorConfigurations);
waitForRegistration();
mFaceService.mServiceWrapper.detectFace(mToken, mFaceServiceReceiver,
faceAuthenticateOptions);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 84c3684..a556f52 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -50,8 +50,6 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -60,7 +58,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -137,6 +134,8 @@
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
@Captor
private ArgumentCaptor<Consumer<OperationContext>> mStartHalConsumerCaptor;
+ @Captor
+ private ArgumentCaptor<FaceAuthenticateOptions> mFaceAuthenticateOptionsCaptor;
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -159,21 +158,26 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
public void authWithContext_v2() throws RemoteException {
final FaceAuthenticationClient client = createClient(2);
client.start(mCallback);
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(),
+ mFaceAuthenticateOptionsCaptor.capture());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor.getValue().toAidlContext(
+ mFaceAuthenticateOptionsCaptor.getValue()));
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+
order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
.isEqualTo(AUTH_REASON);
-
verify(mHal, never()).authenticate(anyLong());
}
@@ -200,30 +204,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceAuthenticationClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceAuthenticationClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index e626f73..fd3f054 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -20,7 +20,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
@@ -37,8 +36,6 @@
import android.os.RemoteException;
import android.os.Vibrator;
import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -46,7 +43,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -58,7 +54,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -124,49 +119,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void detectWithContext_v2() throws RemoteException {
- final FaceDetectClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).detectInteractionWithContext(same(aidlContext));
- assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
- assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
- .isEqualTo(AUTH_REASON);
-
- verify(mHal, never()).detectInteraction();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceDetectClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).detectInteractionWithContext(captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceDetectClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 02363cd..d6b5789 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
@@ -38,8 +37,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -47,7 +44,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -60,7 +56,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -124,45 +119,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithContext_v2() throws RemoteException {
- final FaceEnrollClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), same(aidlContext));
- verify(mHal, never()).enroll(any(), anyByte(), any(), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FaceEnrollClient client = createClient(3);
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).enrollWithContext(any(), anyByte(), any(), any(), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FaceEnrollClient client = createClient(3);
client.start(mCallback);
@@ -192,16 +148,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithFaceOptions() throws RemoteException {
- final FaceEnrollClient client = createClient(4);
- client.start(mCallback);
-
- verify(mHal).enrollWithOptions(any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void enrollWithFaceOptionsAfterSubscribingContext() throws RemoteException {
final FaceEnrollClient client = createClient(4);
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 9eca93e..c4e51f8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -44,22 +44,18 @@
import android.hardware.face.HidlFaceSensorConfig;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -134,13 +130,8 @@
when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
mBiometricCallbackHandler);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
- mLooper.getLooper()));
- } else {
- when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
- Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getFaceHandler()).thenReturn(new Handler(
+ mLooper.getLooper()));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAddingHidlSensors() {
when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -247,7 +237,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAuthenticateCallbackHandler() {
waitForIdle();
@@ -295,10 +284,6 @@
}
private void waitForIdle() {
- if (Flags.deHidl()) {
- mLooper.dispatchAll();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ mLooper.dispatchAll();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
index fe9cd43..6780e60 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/SensorTest.java
@@ -41,7 +41,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -50,7 +49,6 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import org.junit.Before;
@@ -75,12 +73,8 @@
@Mock
private ISession mSession;
@Mock
- private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
- @Mock
private UserSwitchProvider<IFace, ISession> mUserSwitchProvider;
@Mock
- private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
- @Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
private BiometricLogger mBiometricLogger;
@@ -94,6 +88,8 @@
private BaseClientMonitor mClientMonitor;
@Mock
private AidlSession mCurrentSession;
+ @Mock
+ private AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -108,27 +104,17 @@
when(mContext.getSystemService(Context.BIOMETRIC_SERVICE)).thenReturn(mBiometricService);
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- 2 /* recentOperationsLimit */,
- () -> USER_ID,
- mUserSwitchProvider);
- } else {
- mScheduler = new UserAwareBiometricScheduler<>(TAG,
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FACE,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> USER_ID,
- mUserSwitchCallback);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FACE,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ 2 /* recentOperationsLimit */,
+ () -> USER_ID,
+ mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHardwareUnavailableCallback);
+ mAidlResponseHandlerCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
deleted file mode 100644
index 949d6ee..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.Flags;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.IntStream;
-
-@Presubmit
-@SmallTest
-public class Face10Test {
-
- private static final String TAG = "Face10Test";
- private static final int SENSOR_ID = 1;
- private static final int USER_ID = 20;
- private static final float FRR_THRESHOLD = 0.2f;
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
- @Mock
- private Context mContext;
- @Mock
- private UserManager mUserManager;
- @Mock
- private Resources mResources;
- @Mock
- private BiometricScheduler mScheduler;
- @Mock
- private BiometricContext mBiometricContext;
- @Mock
- private BiometricStateCallback mBiometricStateCallback;
- @Mock
- private AuthenticationStateListeners mAuthenticationStateListeners;
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private LockoutResetDispatcher mLockoutResetDispatcher;
- private com.android.server.biometrics.sensors.face.hidl.Face10 mFace10;
- private IBinder mBinder;
-
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
-
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
- .thenReturn(FRR_THRESHOLD);
-
- mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
- final int maxEnrollmentsPerUser = 1;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- final boolean supportsFaceDetection = false;
- final boolean supportsSelfIllumination = false;
- final boolean resetLockoutRequiresChallenge = false;
- final FaceSensorPropertiesInternal sensorProps = new FaceSensorPropertiesInternal(SENSOR_ID,
- SensorProperties.STRENGTH_STRONG, maxEnrollmentsPerUser, componentInfo,
- FaceSensorProperties.TYPE_UNKNOWN, supportsFaceDetection, supportsSelfIllumination,
- resetLockoutRequiresChallenge);
-
- Face10.sSystemClock = Clock.fixed(
- Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
- mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
- sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
- mBinder = new Binder();
- }
-
- private void tick(long seconds) {
- waitForIdle();
- Face10.sSystemClock = Clock.fixed(Instant.ofEpochSecond(
- Face10.sSystemClock.instant().getEpochSecond() + seconds),
- ZoneId.of("America/Los_Angeles"));
- }
-
- @Test
- public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
- assertEquals(0, mFace10.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
- waitForIdle();
- }
-
- @Test
- public void scheduleRevokeChallenge_doesNotCrash() {
- mFace10.scheduleRevokeChallenge(0 /* sensorId */, 0 /* userId */, mBinder, TAG,
- 0 /* challenge */);
- waitForIdle();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleGenerateChallenge_cachesResult() {
- final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
- .mapToObj(i -> mock(IFaceServiceReceiver.class))
- .toArray(IFaceServiceReceiver[]::new);
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
- tick(10);
- }
- tick(120);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- waitForIdle();
-
- verify(mScheduler, times(2))
- .scheduleClientMonitor(isA(FaceGenerateChallengeClient.class), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleRevokeChallenge_waitsUntilEmpty() {
- final long challenge = 22;
- final IFaceServiceReceiver[] mocks = IntStream.range(0, 3)
- .mapToObj(i -> mock(IFaceServiceReceiver.class))
- .toArray(IFaceServiceReceiver[]::new);
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleGenerateChallenge(SENSOR_ID, USER_ID, mBinder, mock, TAG);
- tick(10);
- }
- for (IFaceServiceReceiver mock : mocks) {
- mFace10.scheduleRevokeChallenge(SENSOR_ID, USER_ID, mBinder, TAG, challenge);
- tick(10);
- }
- waitForIdle();
-
- verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void scheduleRevokeChallenge_doesNotWaitForever() {
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- tick(10000);
- mFace10.scheduleGenerateChallenge(
- SENSOR_ID, USER_ID, mBinder, mock(IFaceServiceReceiver.class), TAG);
- mFace10.scheduleRevokeChallenge(
- SENSOR_ID, USER_ID, mBinder, TAG, 8 /* challenge */);
- waitForIdle();
-
- verify(mScheduler).scheduleClientMonitor(isA(FaceRevokeChallengeClient.class), any());
- }
-
- @Test
- public void halServiceDied_resetsScheduler() {
- // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
- // serviceDied directly.
- mFace10.serviceDied(0 /* cookie */);
- waitForIdle();
- verify(mScheduler).reset();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
deleted file mode 100644
index ec08329..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/FaceGenerateChallengeClientTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2021 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.biometrics.sensors.face.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-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.biometrics.face.V1_0.IBiometricsFace;
-import android.hardware.biometrics.face.V1_0.OptionalUint64;
-import android.hardware.face.IFaceServiceReceiver;
-import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.SmallTest;
-
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.log.BiometricLogger;
-import com.android.server.biometrics.sensors.ClientMonitorCallback;
-import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@SmallTest
-public class FaceGenerateChallengeClientTest {
-
- private static final String TAG = "FaceGenerateChallengeClientTest";
- private static final int USER_ID = 2;
- private static final int SENSOR_ID = 4;
- private static final long START_TIME = 5000;
- private static final long CHALLENGE = 200;
-
- private final Context mContext = ApplicationProvider.getApplicationContext();
-
- @Mock
- private IBiometricsFace mIBiometricsFace;
- @Mock
- private IFaceServiceReceiver mClientReceiver;
- @Mock
- private IFaceServiceReceiver mOtherReceiver;
- @Mock
- private ClientMonitorCallback mMonitorCallback;
- @Mock
- private BiometricLogger mBiometricLogger;
- @Mock
- private BiometricContext mBiometricContext;
-
- private FaceGenerateChallengeClient mClient;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- final OptionalUint64 challenge = new OptionalUint64();
- challenge.value = CHALLENGE;
- when(mIBiometricsFace.generateChallenge(anyInt())).thenReturn(challenge);
-
- mClient = new FaceGenerateChallengeClient(mContext, () -> mIBiometricsFace, new Binder(),
- new ClientMonitorCallbackConverter(mClientReceiver), USER_ID,
- TAG, SENSOR_ID, mBiometricLogger, mBiometricContext , START_TIME);
- }
-
- @Test
- public void getCreatedAt() {
- assertEquals(START_TIME, mClient.getCreatedAt());
- }
-
- @Test
- public void reuseResult_whenNotReady() throws Exception {
- mClient.reuseResult(mOtherReceiver);
- verify(mOtherReceiver, never()).onChallengeGenerated(anyInt(), anyInt(), anyInt());
- }
-
- @Test
- public void reuseResult_whenReady() throws Exception {
- mClient.start(mMonitorCallback);
- mClient.reuseResult(mOtherReceiver);
- verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
- }
-
- @Test
- public void reuseResult_whenReallyReady() throws Exception {
- mClient.reuseResult(mOtherReceiver);
- mClient.start(mMonitorCallback);
- verify(mOtherReceiver).onChallengeGenerated(eq(SENSOR_ID), eq(USER_ID), eq(CHALLENGE));
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
index b5d73d2..44da431 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSessionAdapterTest.java
@@ -16,8 +16,8 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static com.android.server.biometrics.sensors.face.hidl.FaceGenerateChallengeClient.CHALLENGE_TIMEOUT_SEC;
import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.ENROLL_TIMEOUT_SEC;
+import static com.android.server.biometrics.sensors.face.hidl.HidlToAidlSessionAdapter.CHALLENGE_TIMEOUT_SEC;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 9a8cd48..6126af5 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -51,7 +51,6 @@
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
@@ -64,7 +63,6 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintProvider;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -90,8 +88,6 @@
private static final int ID_VIRTUAL = 6;
private static final String NAME_DEFAULT = "default";
private static final String NAME_VIRTUAL = "virtual";
- private static final List<FingerprintSensorPropertiesInternal> HIDL_AUTHENTICATORS =
- List.of();
private static final String OP_PACKAGE_NAME = "FingerprintServiceTest/SystemUi";
@Rule
@@ -185,7 +181,7 @@
private void initServiceWithAndWait(String... aidlInstances) throws Exception {
initServiceWith(aidlInstances);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
}
@@ -193,18 +189,7 @@
public void registerAuthenticators_defaultOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_defaultOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
@@ -216,7 +201,7 @@
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -228,20 +213,7 @@
Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
- initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
- Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
- Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 1);
-
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
@@ -249,23 +221,12 @@
@Test
public void registerAuthenticators_virtualAlwaysWhenNoOther() throws Exception {
- initServiceWith(NAME_VIRTUAL);
-
- mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
- waitForRegistration();
-
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
- public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFingerprintSensorConfigurations =
new FingerprintSensorConfigurations(true);
mFingerprintSensorConfigurations.addAidlSensors(new String[]{NAME_VIRTUAL});
initServiceWith(NAME_VIRTUAL);
- mService.mServiceWrapper.registerAuthenticatorsLegacy(mFingerprintSensorConfigurations);
+ mService.mServiceWrapper.registerAuthenticators(mFingerprintSensorConfigurations);
waitForRegistration();
verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 7a77392..db9fe7f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -30,7 +30,6 @@
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.same;
@@ -42,7 +41,6 @@
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
@@ -52,13 +50,10 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -67,7 +62,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -84,7 +78,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -180,36 +173,18 @@
public void authNoContext_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
verify(mHal).authenticate(eq(OP_ID));
verify(mHal, never()).authenticateWithContext(anyLong(), any());
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void authWithContext_v2() throws RemoteException {
- final FingerprintAuthenticationClient client = createClient(2);
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
- assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
- .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
- verify(mHal, never()).authenticate(anyLong());
- }
-
- @Test
public void pointerUp_v1() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1);
client.start(mCallback);
@@ -277,21 +252,18 @@
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
final ArgumentCaptor<OperationContext> captor =
ArgumentCaptor.forClass(OperationContext.class);
verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
OperationContext opContext = captor.getValue();
- if (!Flags.deHidl()) {
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- }
+
assertThat(mOperationContextCaptor.getValue().toAidlContext())
.isSameInstanceAs(opContext);
@@ -326,12 +298,12 @@
when(mBiometricContext.isAod()).thenReturn(false);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
verify(mLuxProbe, isAwake ? times(1) : never()).enable();
}
@@ -342,21 +314,18 @@
when(mBiometricContext.isAod()).thenReturn(true);
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
- if (Flags.deHidl()) {
- verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
- mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
- mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
- .getValue().toAidlContext());
- }
+
+ verify(mBiometricContext).subscribe(mOperationContextCaptor.capture(),
+ mStartHalConsumerCaptor.capture(), mContextInjector.capture(), any());
+
+ mStartHalConsumerCaptor.getValue().accept(mOperationContextCaptor
+ .getValue().toAidlContext());
final ArgumentCaptor<OperationContext> captor =
ArgumentCaptor.forClass(OperationContext.class);
verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
OperationContext opContext = captor.getValue();
- if (!Flags.deHidl()) {
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- }
+
assertThat(opContext).isSameInstanceAs(
mOperationContextCaptor.getValue().toAidlContext());
@@ -380,30 +349,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintAuthenticationClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).authenticateWithContext(eq(OP_ID), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintAuthenticationClient client = createClient();
client.start(mCallback);
@@ -603,7 +548,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testLockoutTracker_authFailed() throws RemoteException {
final FingerprintAuthenticationClient client = createClient(1 /* version */,
true /* allowBackgroundAuthentication */, mClientMonitorCallbackConverter,
@@ -658,8 +602,7 @@
null /* taskStackListener */,
mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners,
allowBackgroundAuthentication,
- mSensorProps,
- new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
+ mSensorProps, 0 /* biometricStrength */,
lockoutTracker) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 9edb8dd..6b8c3cd 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -21,13 +21,11 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -35,8 +33,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.testing.TestableContext;
@@ -44,7 +40,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
@@ -56,7 +51,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -119,49 +113,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void detectNoContext_v2() throws RemoteException {
- final FingerprintDetectClient client = createClient(2);
-
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
-
- final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
- order.verify(mHal).detectInteractionWithContext(same(aidlContext));
- assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
- .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
-
- verify(mHal, never()).detectInteraction();
- }
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintDetectClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).detectInteractionWithContext(captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- assertThat(opContext).isSameInstanceAs(
- mOperationContextCaptor.getValue().toAidlContext());
- mContextInjector.getValue().accept(opContext);
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintDetectClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 916f696..d2e1c3c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -24,7 +24,6 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.times;
@@ -44,8 +43,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-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.platform.test.flag.junit.SetFlagsRule;
@@ -54,7 +51,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -70,7 +66,6 @@
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -156,21 +151,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void enrollWithContext_v2() throws RemoteException {
- final FingerprintEnrollClient client = createClient(2);
-
- client.start(mCallback);
-
- InOrder order = inOrder(mHal, mBiometricContext);
- order.verify(mBiometricContext).updateContext(
- mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).enrollWithContext(any(),
- same(mOperationContextCaptor.getValue().toAidlContext()));
- verify(mHal, never()).enroll(any());
- }
-
- @Test
public void pointerUp_v1() throws RemoteException {
final FingerprintEnrollClient client = createClient(1);
client.start(mCallback);
@@ -253,29 +233,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_DE_HIDL)
- public void notifyHalWhenContextChanges() throws RemoteException {
- final FingerprintEnrollClient client = createClient();
- client.start(mCallback);
-
- final ArgumentCaptor<OperationContext> captor =
- ArgumentCaptor.forClass(OperationContext.class);
- verify(mHal).enrollWithContext(any(), captor.capture());
- OperationContext opContext = captor.getValue();
-
- // fake an update to the context
- verify(mBiometricContext).subscribe(
- mOperationContextCaptor.capture(), mContextInjector.capture());
- mContextInjector.getValue().accept(
- mOperationContextCaptor.getValue().toAidlContext());
- verify(mHal).onContextChanged(same(opContext));
-
- client.stopHalOperation();
- verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void subscribeContextAndStartHal() throws RemoteException {
final FingerprintEnrollClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 0a35037..1f288b2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -47,21 +47,17 @@
import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.biometrics.BiometricHandlerProvider;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationStateListeners;
@@ -136,13 +132,8 @@
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mBiometricHandlerProvider.getBiometricCallbackHandler()).thenReturn(
mBiometricCallbackHandler);
- if (Flags.deHidl()) {
- when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
- new Handler(mLooper.getLooper()));
- } else {
- when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
- new Handler(Looper.getMainLooper()));
- }
+ when(mBiometricHandlerProvider.getFingerprintHandler()).thenReturn(
+ new Handler(mLooper.getLooper()));
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
@@ -176,7 +167,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testAddingHidlSensors() {
when(mResources.getIntArray(anyInt())).thenReturn(new int[]{});
when(mResources.getBoolean(anyInt())).thenReturn(false);
@@ -252,7 +242,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void testScheduleAuthenticate() {
waitForIdle();
@@ -302,10 +291,6 @@
}
private void waitForIdle() {
- if (Flags.deHidl()) {
- mLooper.dispatchAll();
- } else {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
+ mLooper.dispatchAll();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
index b4c2ee8..698db2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/SensorTest.java
@@ -42,7 +42,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
@@ -51,7 +50,6 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.LockoutTracker;
-import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.UserSwitchProvider;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -77,12 +75,8 @@
@Mock
private ISession mSession;
@Mock
- private UserAwareBiometricScheduler.UserSwitchCallback mUserSwitchCallback;
- @Mock
private UserSwitchProvider<IFingerprint, ISession> mUserSwitchProvider;
@Mock
- private AidlResponseHandler.HardwareUnavailableCallback mHardwareUnavailableCallback;
- @Mock
private LockoutResetDispatcher mLockoutResetDispatcher;
@Mock
private BiometricLogger mLogger;
@@ -100,6 +94,8 @@
private BaseClientMonitor mClientMonitor;
@Mock
private HandlerThread mThread;
+ @Mock
+ AidlResponseHandler.AidlResponseHandlerCallback mAidlResponseHandlerCallback;
private final TestLooper mLooper = new TestLooper();
private final LockoutCache mLockoutCache = new LockoutCache();
@@ -115,27 +111,17 @@
when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
when(mThread.getLooper()).thenReturn(mLooper.getLooper());
- if (Flags.deHidl()) {
- mScheduler = new BiometricScheduler<>(
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- 2 /* recentOperationsLimit */,
- () -> USER_ID,
- mUserSwitchProvider);
- } else {
- mScheduler = new UserAwareBiometricScheduler<>(TAG,
- new Handler(mLooper.getLooper()),
- BiometricScheduler.SENSOR_TYPE_FP_OTHER,
- null /* gestureAvailabilityDispatcher */,
- mBiometricService,
- () -> USER_ID,
- mUserSwitchCallback);
- }
+ mScheduler = new BiometricScheduler<>(
+ new Handler(mLooper.getLooper()),
+ BiometricScheduler.SENSOR_TYPE_FP_OTHER,
+ null /* gestureAvailabilityDispatcher */,
+ mBiometricService,
+ 2 /* recentOperationsLimit */,
+ () -> USER_ID,
+ mUserSwitchProvider);
mHalCallback = new AidlResponseHandler(mContext, mScheduler, SENSOR_ID, USER_ID,
mLockoutCache, mLockoutResetDispatcher, mAuthSessionCoordinator,
- mHardwareUnavailableCallback);
+ mAidlResponseHandlerCallback);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
deleted file mode 100644
index 0d3f192..0000000
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2020 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.biometrics.sensors.fingerprint.hidl;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.hardware.biometrics.ComponentInfoInternal;
-import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.server.biometrics.log.BiometricContext;
-import com.android.server.biometrics.sensors.AuthenticationStateListeners;
-import com.android.server.biometrics.sensors.BiometricScheduler;
-import com.android.server.biometrics.sensors.BiometricStateCallback;
-import com.android.server.biometrics.sensors.LockoutResetDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@SmallTest
-public class Fingerprint21Test {
-
- private static final String TAG = "Fingerprint21Test";
- private static final int SENSOR_ID = 1;
-
- @Mock
- private Context mContext;
- @Mock
- private Resources mResources;
- @Mock
- private UserManager mUserManager;
- @Mock
- Fingerprint21.HalResultController mHalResultController;
- @Mock
- private BiometricScheduler mScheduler;
- @Mock
- private AuthenticationStateListeners mAuthenticationStateListeners;
- @Mock
- private BiometricStateCallback mBiometricStateCallback;
- @Mock
- private BiometricContext mBiometricContext;
-
- private LockoutResetDispatcher mLockoutResetDispatcher;
- private Fingerprint21 mFingerprint21;
-
- private static void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
- when(mContext.getResources()).thenReturn(mResources);
- when(mResources.getInteger(eq(R.integer.config_fingerprintMaxTemplatesPerUser)))
- .thenReturn(5);
-
- mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
-
- final int maxEnrollmentsPerUser = 1;
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- final boolean resetLockoutRequiresHardwareAuthToken = false;
- final FingerprintSensorPropertiesInternal sensorProps =
- new FingerprintSensorPropertiesInternal(SENSOR_ID,
- FingerprintSensorProperties.STRENGTH_WEAK, maxEnrollmentsPerUser,
- componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
- resetLockoutRequiresHardwareAuthToken);
-
- mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback,
- mAuthenticationStateListeners, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController,
- mBiometricContext);
- }
-
- @Test
- public void getAuthenticatorId_doesNotCrashWhenIdNotFound() {
- assertEquals(0, mFingerprint21.getAuthenticatorId(0 /* sensorId */, 111 /* userId */));
- waitForIdle();
- }
-
- @Test
- public void halServiceDied_resetsScheduler() {
- // It's difficult to test the linkToDeath --> serviceDied path, so let's just invoke
- // serviceDied directly.
- mFingerprint21.serviceDied(0 /* cookie */);
- waitForIdle();
- verify(mScheduler).reset();
- }
-
- private static class TestableFingerprint21 extends Fingerprint21 {
-
- TestableFingerprint21(@NonNull Context context,
- @NonNull BiometricStateCallback biometricStateCallback,
- @NonNull AuthenticationStateListeners authenticationStateListeners,
- @NonNull FingerprintSensorPropertiesInternal sensorProps,
- @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
- @NonNull LockoutResetDispatcher lockoutResetDispatcher,
- @NonNull HalResultController controller,
- @NonNull BiometricContext biometricContext) {
- super(context, biometricStateCallback, authenticationStateListeners, sensorProps,
- scheduler, handler, lockoutResetDispatcher, controller, biometricContext);
- }
-
- @Override
- synchronized IBiometricsFingerprint getDaemon() {
- return mock(IBiometricsFingerprint.class);
- }
- }
-}
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 74e854e4..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,10 +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(anyString(), anyString());
- doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when(
- mIInputManagerMock).removeUniqueIdAssociation(anyString());
+ doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
+ inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
+ anyString(), 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.
@@ -112,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/Android.bp b/services/tests/uiservicestests/Android.bp
index 515898a..e6cf0c38 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -50,6 +50,7 @@
"SettingsLib",
"libprotobuf-java-lite",
"platformprotoslite",
+ "platform-parametric-runner-lib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index ad25d76..770712a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -26,14 +26,18 @@
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.media.AudioAttributes.USAGE_UNKNOWN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.notification.NotificationChannelExtractor.RESTRICT_AUDIO_ATTRIBUTES;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Flags;
@@ -43,12 +47,14 @@
import android.app.Person;
import android.media.AudioAttributes;
import android.net.Uri;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
+import com.android.internal.compat.IPlatformCompat;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -60,6 +66,8 @@
public class NotificationChannelExtractorTest extends UiServiceTestCase {
@Mock RankingConfig mConfig;
+ @Mock
+ IPlatformCompat mPlatformCompat;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -73,6 +81,7 @@
mExtractor = new NotificationChannelExtractor();
mExtractor.setConfig(mConfig);
mExtractor.initialize(mContext, null);
+ mExtractor.setCompatChangeLogger(mPlatformCompat);
}
private NotificationRecord getRecord(NotificationChannel channel, Notification n) {
@@ -82,7 +91,7 @@
}
@Test
- public void testExtractsUpdatedConversationChannel() {
+ public void testExtractsUpdatedConversationChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -101,7 +110,7 @@
}
@Test
- public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() {
+ public void testInvalidShortcutFlagEnabled_looksUpCorrectNonChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -122,7 +131,7 @@
}
@Test
- public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() {
+ public void testInvalidShortcutFlagDisabled_looksUpCorrectChannel() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
final Notification n = new Notification.Builder(getContext())
.setContentTitle("foo")
@@ -143,7 +152,7 @@
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
- public void testAudioAttributes_callStyleCanUseCallUsage() {
+ public void testAudioAttributes_callStyleCanUseCallUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -162,11 +171,12 @@
assertThat(mExtractor.process(r)).isNull();
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
assertThat(r.getChannel()).isEqualTo(channel);
+ verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_CALL)
- public void testAudioAttributes_nonCallStyleCannotUseCallUsage() {
+ public void testAudioAttributes_nonCallStyleCannotUseCallUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_NOTIFICATION_RINGTONE)
@@ -180,13 +190,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION_RINGTONE);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
- public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() {
+ public void testAudioAttributes_alarmCategoryCanUseAlarmUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_ALARM)
@@ -201,11 +212,12 @@
assertThat(mExtractor.process(r)).isNull();
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
assertThat(r.getChannel()).isEqualTo(channel);
+ verify(mPlatformCompat, never()).reportChangeByUid(anyLong(), anyInt());
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_ALARM)
- public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() {
+ public void testAudioAttributes_nonAlarmCategoryCannotUseAlarmUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_ALARM)
@@ -219,13 +231,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_ALARM);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
- public void testAudioAttributes_noMediaUsage() {
+ public void testAudioAttributes_noMediaUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_MEDIA)
@@ -239,13 +252,14 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_MEDIA);
}
@Test
@EnableFlags(Flags.FLAG_RESTRICT_AUDIO_ATTRIBUTES_MEDIA)
- public void testAudioAttributes_noUnknownUsage() {
+ public void testAudioAttributes_noUnknownUsage() throws RemoteException {
NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
channel.setSound(Uri.EMPTY, new AudioAttributes.Builder()
.setUsage(USAGE_UNKNOWN)
@@ -259,6 +273,7 @@
assertThat(mExtractor.process(r)).isNull();
// instance updated
assertThat(r.getAudioAttributes().getUsage()).isEqualTo(USAGE_NOTIFICATION);
+ verify(mPlatformCompat).reportChangeByUid(RESTRICT_AUDIO_ATTRIBUTES, r.getUid());
// in-memory channel unchanged
assertThat(channel.getAudioAttributes().getUsage()).isEqualTo(USAGE_UNKNOWN);
}
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 4a61d32..011f2e3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -106,22 +106,23 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
+import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL;
+import static com.android.server.notification.NotificationManagerService.TAG;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
-
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -130,7 +131,6 @@
import static junit.framework.Assert.assertSame;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
-
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.isNull;
@@ -138,10 +138,24 @@
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
import android.Manifest;
import android.annotation.Nullable;
@@ -168,6 +182,8 @@
import android.app.RemoteInputHistoryItem;
import android.app.StatsManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.job.JobScheduler;
+import android.app.role.RoleManager;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -184,9 +200,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
+import android.content.pm.ModuleInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
@@ -217,6 +235,7 @@
import android.permission.PermissionManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.rule.LimitDevicesRule;
import android.provider.DeviceConfig;
@@ -235,7 +254,8 @@
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
import android.telecom.TelecomManager;
-import android.testing.AndroidTestingRunner;
+import android.testing.TestWithLooperRule;
+import android.testing.TestableContentResolver;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestablePermissions;
@@ -245,13 +265,13 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Pair;
import android.util.Xml;
+import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-
import com.android.internal.R;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
@@ -259,6 +279,7 @@
import com.android.internal.logging.InstanceIdSequenceFake;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.widget.LockPatternUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.DeviceIdleInternal;
@@ -282,13 +303,10 @@
import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-
import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
-
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -306,6 +324,8 @@
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -322,9 +342,9 @@
import java.util.function.Consumer;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -369,6 +389,8 @@
@Mock
private PermissionHelper mPermissionHelper;
private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
+ @Rule(order = Integer.MAX_VALUE)
+ public TestWithLooperRule mlooperRule = new TestWithLooperRule();
private TestableLooper mTestableLooper;
@Mock
private RankingHelper mRankingHelper;
@@ -415,8 +437,8 @@
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
- @Mock
- IIntentSender pi1;
+ private PendingIntent mActivityIntent;
+ private PendingIntent mActivityIntentImmutable;
private static final int MAX_POST_DELAY = 1000;
@@ -429,6 +451,9 @@
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
private static final String SEARCH_SELECTOR_PKG = "searchSelector";
+ private static final String ADSERVICES_MODULE_PKG = "com.android.adservices";
+ private static final String ADSERVICES_APK_PKG = "com.android.adservices.api";
+
@Mock
private NotificationListeners mListeners;
@Mock
@@ -465,6 +490,7 @@
StatsManager mStatsManager;
@Mock
AlarmManager mAlarmManager;
+ @Mock JobScheduler mJobScheduler;
@Mock
MultiRateLimiter mToastRateLimiter;
BroadcastReceiver mPackageIntentReceiver;
@@ -508,6 +534,16 @@
}
}
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ FLAG_ALL_NOTIFS_NEED_TTL);
+ }
+
+ public NotificationManagerServiceTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
// Shell permisssions will override permissions of our app, so add all necessary permissions
@@ -540,12 +576,22 @@
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal);
+ LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.addService(ShortcutServiceInternal.class, mShortcutServiceInternal);
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
mContext.addMockSystemService(NotificationManager.class, mMockNm);
+ mContext.addMockSystemService(RoleManager.class, mock(RoleManager.class));
+ mContext.addMockSystemService(Context.LAUNCHER_APPS_SERVICE, mLauncherApps);
+ mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE,
+ mock(AccessibilityManager.class));
doNothing().when(mContext).sendBroadcast(any(), anyString());
doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
+ TestableContentResolver cr = mock(TestableContentResolver.class);
+ when(mContext.getContentResolver()).thenReturn(cr);
+ doNothing().when(cr).registerContentObserver(any(), anyBoolean(), any(), anyInt());
setDpmAppOppsExemptFromDismissal(false);
@@ -648,11 +694,16 @@
});
// TODO (b/291907312): remove feature flag
- // NOTE: Prefer using the @EnableFlag annotation where possible. Do not add any android.app
+ // NOTE: Prefer using the @EnableFlags annotation where possible. Do not add any android.app
// flags here.
mSetFlagsRule.disableFlags(
Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE);
+ mActivityIntent = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), PendingIntent.FLAG_MUTABLE));
+ mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mPkg), FLAG_IMMUTABLE));
+
initNMS();
}
@@ -689,10 +740,15 @@
mPowerManager, mPostNotificationTrackerFactory);
mService.setAttentionHelper(mAttentionHelper);
+ mService.setLockPatternUtils(mock(LockPatternUtils.class));
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
-
+ ModuleInfo moduleInfo = new ModuleInfo();
+ moduleInfo.setApexModuleName(ADSERVICES_MODULE_PKG);
+ moduleInfo.setApkInApexPackageNames(List.of(ADSERVICES_APK_PKG));
+ when(mPackageManagerClient.getInstalledModules(anyInt()))
+ .thenReturn(List.of(moduleInfo));
if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) {
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
}
@@ -749,7 +805,10 @@
}
assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver);
- assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver);
+ if (!Flags.allNotifsNeedTtl()) {
+ assertNotNull("Notification timeout receiver should exist",
+ mNotificationTimeoutReceiver);
+ }
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -834,9 +893,17 @@
if (mFile != null) mFile.delete();
clearDeviceConfig();
+ if (mActivityIntent != null) {
+ mActivityIntent.cancel();
+ }
+
+ mService.clearNotifications();
+ TestableLooper.get(this).processAllMessages();
+
try {
mService.onDestroy();
} catch (IllegalStateException | IllegalArgumentException e) {
+ Log.e(TAG, "failed to destroy", e);
// can throw if a broadcast receiver was never registered
}
@@ -846,6 +913,11 @@
// could cause issues, for example, messages that remove/cancel shown toasts (this causes
// problematic interactions with mocks when they're no longer working as expected).
mWorkerHandler.removeCallbacksAndMessages(null);
+
+ if (TestableLooper.get(this) != null) {
+ // Must remove static reference to this test object to prevent leak (b/261039202)
+ TestableLooper.remove(this);
+ }
}
private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
@@ -1005,7 +1077,9 @@
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
- .addAction(new Notification.Action.Builder(null, "test", null).build());
+ .addAction(new Notification.Action.Builder(null, "test", mActivityIntent).build())
+ .addAction(new Notification.Action.Builder(
+ null, "test", mActivityIntentImmutable).build());
if (extender != null) {
nb.extend(extender);
}
@@ -1045,18 +1119,20 @@
private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
String tag) {
- return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+ return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false, true);
}
private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
- NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+ NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary,
+ boolean mutable) {
if (channel == null) {
channel = mTestNotificationChannel;
}
if (tag == null) {
tag = "tag";
}
- Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+ Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey,
+ isSummary, mutable);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id,
tag, mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -1129,18 +1205,15 @@
}
private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
- String groupKey, boolean isSummary) {
+ String groupKey, boolean isSummary, boolean mutable) {
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
.build();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mutable ? mActivityIntent : mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
// Make it messaging style
Notification.Builder nb = new Notification.Builder(mContext,
@@ -1167,17 +1240,14 @@
}
private Notification.BubbleMetadata getBubbleMetadata() {
- PendingIntent pendingIntent = mock(PendingIntent.class);
- Intent intent = mock(Intent.class);
- when(pendingIntent.getIntent()).thenReturn(intent);
- when(pendingIntent.getTarget()).thenReturn(pi1);
-
ActivityInfo info = new ActivityInfo();
info.resizeMode = RESIZE_MODE_RESIZEABLE;
- when(intent.resolveActivityInfo(any(), anyInt())).thenReturn(info);
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = info;
+ when(mPackageManagerClient.resolveActivity(any(), anyInt())).thenReturn(ri);
return new Notification.BubbleMetadata.Builder(
- pendingIntent,
+ mActivityIntent,
Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon))
.build();
}
@@ -1189,7 +1259,8 @@
// Notification that has bubble metadata
NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
- mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrBubble.getSbn().getTag(),
nrBubble.getSbn().getId(), nrBubble.getSbn().getNotification(),
@@ -1203,7 +1274,8 @@
// Notification without bubble metadata
NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+ mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nrPlain.getSbn().getTag(),
nrPlain.getSbn().getId(), nrPlain.getSbn().getNotification(),
@@ -1215,7 +1287,8 @@
// Summary notification for both of those
NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
- mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+ mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */,
+ true);
if (summaryAutoCancel) {
nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
@@ -1232,6 +1305,7 @@
}
@Test
+ @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testLimitTimeOutBroadcast() {
NotificationChannel channel = new NotificationChannel("id", "name",
NotificationManager.IMPORTANCE_HIGH);
@@ -2501,8 +2575,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelWithTagDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -2529,19 +2603,11 @@
assertThat(captor.getValue().getNotification().flags
& FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
-
- mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
- mBinderService.cancelNotificationWithTag(mPkg, mPkg, sbn.getTag(), sbn.getId(),
- sbn.getUserId());
- waitForIdle();
-
- assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(0);
- assertThat(mService.getNotificationRecordCount()).isEqualTo(0);
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelAllDoesNotCancelLifetimeExtended() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Adds a lifetime extended notification.
final NotificationRecord notif = generateNotificationRecord(mTestNotificationChannel, 1,
null, false);
@@ -2978,9 +3044,9 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 1, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -3211,9 +3277,9 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testCancelNotificationsFromListener_byKey_NoClearLifetimeExt()
throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord notif = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
@@ -5695,12 +5761,16 @@
verify(mZenModeHelper).updateZenRulesOnLocaleChange();
}
- private void simulateNotificationTimeoutBroadcast(String notificationKey) {
- final Bundle extras = new Bundle();
- extras.putString(EXTRA_KEY, notificationKey);
- final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
- intent.putExtras(extras);
- mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ private void simulateNotificationTimeout(String notificationKey) {
+ if (Flags.allNotifsNeedTtl()) {
+ mService.mNotificationManagerPrivate.timeoutNotification(notificationKey);
+ } else {
+ final Bundle extras = new Bundle();
+ extras.putString(EXTRA_KEY, notificationKey);
+ final Intent intent = new Intent(ACTION_NOTIFICATION_TIMEOUT);
+ intent.putExtras(extras);
+ mNotificationTimeoutReceiver.onReceive(getContext(), intent);
+ }
}
@Test
@@ -5709,7 +5779,7 @@
mTestNotificationChannel, 1, null, false);
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was cancelled.
@@ -5725,7 +5795,7 @@
notif.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5741,7 +5811,7 @@
notif.getSbn().getNotification().flags = Notification.FLAG_USER_INITIATED_JOB;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5751,15 +5821,15 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testTimeout_NoCancelLifetimeExtensionNotification() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
// Create a notification with FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
final NotificationRecord notif = generateNotificationRecord(null);
notif.getSbn().getNotification().flags =
Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(notif);
- simulateNotificationTimeoutBroadcast(notif.getKey());
+ simulateNotificationTimeout(notif.getKey());
waitForIdle();
// Check that the notification was not cancelled.
@@ -5839,8 +5909,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_DirectReplyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
r.getSbn().getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
mService.addNotification(r);
@@ -6662,6 +6732,7 @@
verify(visitor, times(1)).accept(eq(personIcon.getUri()));
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(hangUpUri));
+ hangUpIntent.cancel();
}
@Test
@@ -6691,6 +6762,8 @@
verify(visitor, times(1)).accept(eq(verificationIcon.getUri()));
verify(visitor, times(1)).accept(eq(answerIntent.getIntent().getData()));
verify(visitor, times(1)).accept(eq(declineUri));
+ answerIntent.cancel();
+ declineIntent.cancel();
}
@Test
@@ -6767,6 +6840,9 @@
verify(visitor, times(1)).accept(eq(actionIntentUri));
verify(visitor).accept(eq(wearActionIcon.getUri()));
verify(visitor, times(1)).accept(eq(wearActionIntentUri));
+ displayIntent.cancel();
+ actionIntent.cancel();
+ wearActionIntent.cancel();
}
@Test
@@ -6788,6 +6864,8 @@
verify(visitor, times(1)).accept(eq(contentIntentUri));
verify(visitor, times(1)).accept(eq(deleteIntentUri));
+ contentIntent.cancel();
+ deleteIntent.cancel();
}
@Test
@@ -6813,6 +6891,8 @@
verify(visitor, times(1)).accept(eq(readPendingIntentUri));
verify(visitor, times(1)).accept(eq(replyPendingIntentUri));
+ readPendingIntent.cancel();
+ replyPendingIntent.cancel();
}
@Test
@@ -8587,8 +8667,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testOnNotificationSmartReplySent() {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8613,8 +8693,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testStats_SmartReplyAlreadyLifetimeExtendedPostsUpdate() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
final int replyIndex = 2;
final String reply = "Hello";
final boolean modifiedBeforeSending = true;
@@ -8647,8 +8727,7 @@
public void testOnNotificationActionClick() {
final int actionIndex = 2;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = false;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -8669,9 +8748,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testActionClickLifetimeExtendedCancel() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
final Notification.Action action =
new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
@@ -8797,8 +8875,7 @@
public void testOnAssistantNotificationActionClick() {
final int actionIndex = 1;
final Notification.Action action =
- new Notification.Action.Builder(null, "text", PendingIntent.getActivity(
- mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)).build();
+ new Notification.Action.Builder(null, "text", mActivityIntent).build();
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
@@ -9313,7 +9390,7 @@
BUBBLE_PREFERENCE_ALL /* app */,
true /* channel */);
- Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false);
+ Notification.Builder nb = getMessageStyleNotifBuilder(true, null, false, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
null, mUid, 0,
@@ -9357,7 +9434,7 @@
// Messaging notif WITHOUT bubble metadata
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
@@ -10615,7 +10692,7 @@
Notification.BubbleMetadata metadata =
new Notification.BubbleMetadata.Builder(VALID_CONVO_SHORTCUT_ID).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -10675,7 +10752,7 @@
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
shortcutId).build();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(shortcutId);
nb.setBubbleMetadata(metadata);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11068,7 +11145,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11104,7 +11181,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11148,7 +11225,7 @@
//Create notification record
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11193,7 +11270,7 @@
//Create notification record without a shortcutId
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
nb.setChannelId(originalChannel.getId());
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
@@ -11328,7 +11405,7 @@
@Test
public void testRecordMessages_invalidMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_invalidMsg", mUid, 0, nb.build(),
@@ -11369,7 +11446,7 @@
@Test
public void testRecordMessages_validMsg() throws RemoteException {
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(PKG_P, PKG_P, 1,
"testRecordMessages_validMsg", mUid, 0, nb.build(),
@@ -11405,7 +11482,7 @@
waitForIdle();
Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
- null /* groupKey */, false /* isSummary */);
+ null /* groupKey */, false /* isSummary */, true);
nb.setShortcutId(null);
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1,
"testRecordMessages_invalidMsg_afterValidMsg_2", mUid, 0, nb.build(),
@@ -11625,10 +11702,9 @@
@Test
public void testImmutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false, false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11642,10 +11718,8 @@
@Test
public void testMutableBubbleIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(pi1))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(true,
- mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false, true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11658,10 +11732,10 @@
@Test
public void testImmutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false,
+ false);
try {
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11675,10 +11749,9 @@
@Test
public void testMutableDirectReplyActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateMessageBubbleNotifRecord(false,
- mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false);
+ mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false,
+ true);
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11690,18 +11763,15 @@
@Test
public void testImmutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_IMMUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntentImmutable).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11722,18 +11792,13 @@
@Test
public void testMutableDirectReplyContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0,
- new Intent().setPackage(mContext.getPackageName()),
- PendingIntent.FLAG_MUTABLE);
Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
+ mActivityIntent).addRemoteInput(remoteInput)
.build();
extraAction.add(replyAction);
Bundle signals = new Bundle();
@@ -11749,10 +11814,8 @@
@Test
public void testImmutableActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
-
mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId());
@@ -11764,12 +11827,11 @@
@Test
public void testImmutableContextualActionIntent() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
+ when(mAmi.getPendingIntentFlags(any())).thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
ArrayList<Notification.Action> extraAction = new ArrayList<>();
- extraAction.add(new Notification.Action(0, "hello", null));
+ extraAction.add(new Notification.Action(0, "hello", mActivityIntentImmutable));
Bundle signals = new Bundle();
signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction);
Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "",
@@ -12121,8 +12183,6 @@
@Test
public void testCallNotificationsBypassBlock() throws Exception {
- when(mAmi.getPendingIntentFlags(any(IIntentSender.class)))
- .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT);
when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
Notification.Builder nb = new Notification.Builder(
@@ -12145,8 +12205,8 @@
.setName("caller")
.build();
nb.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12214,8 +12274,8 @@
mService.clearNotifications();
reset(mUsageStats);
Person person = new Person.Builder().setName("caller").build();
- nb.setStyle(Notification.CallStyle.forOngoingCall(person, mock(PendingIntent.class)));
- nb.setFullScreenIntent(mock(PendingIntent.class), true);
+ nb.setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent));
+ nb.setFullScreenIntent(mActivityIntent, true);
sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0, nb.build(),
UserHandle.getUserHandleForUid(mUid), null, 0);
r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
@@ -12709,7 +12769,7 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12744,7 +12804,7 @@
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
mService.maybeShowInitialReviewPermissionsNotification();
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
}
@@ -12760,7 +12820,7 @@
mInternalService.sendReviewPermissionsNotification();
// Notification should be sent
- verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG),
+ verify(mMockNm, times(1)).notify(eq(TAG),
eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS),
any(Notification.class));
@@ -12792,7 +12852,7 @@
.thenReturn(permissionState);
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
+ .setFullScreenIntent(mActivityIntent, true)
.build();
mService.fixNotification(n, mPkg, "tag", 9, mUserId, mUid, NOT_FOREGROUND_SERVICE, true);
@@ -12860,7 +12920,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -12881,7 +12941,7 @@
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_FOREGROUND_SERVICE, true)
.setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13040,8 +13100,7 @@
Notification n = new Notification.Builder(mContext, "test")
// Without FLAG_FOREGROUND_SERVICE.
//.setFlag(FLAG_FOREGROUND_SERVICE, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13057,8 +13116,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13072,9 +13130,8 @@
public void checkCallStyleNotification_allowedForFsiAllowed() throws Exception {
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
- .setFullScreenIntent(mock(PendingIntent.class), true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setFullScreenIntent(mActivityIntent, true)
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13089,8 +13146,7 @@
Person person = new Person.Builder().setName("caller").build();
Notification n = new Notification.Builder(mContext, "test")
.setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
n, UserHandle.getUserHandleForUid(mUid), null, 0);
@@ -13170,6 +13226,33 @@
}
@Test
+ public void fixSystemNotification_defaultAdservices_withOnGoingFlag_nondismissible()
+ throws Exception {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = ADSERVICES_APK_PKG;
+ ai.uid = mUid;
+ ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(ai);
+ when(mAppOpsManager.checkOpNoThrow(
+ AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid,
+ ai.packageName)).thenReturn(AppOpsManager.MODE_IGNORED);
+ // Given: a notification from an app on the system partition has the flag
+ // FLAG_ONGOING_EVENT set
+ Notification n = new Notification.Builder(mContext, "test")
+ .setOngoing(true)
+ .build();
+
+ // When: fix the notification with NotificationManagerService
+ mService.fixNotification(n, ADSERVICES_APK_PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE,
+ true);
+
+ // Then: the notification's flag FLAG_NO_DISMISS should be set
+ assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS);
+ }
+
+ @Test
public void fixCallNotification_withOnGoingFlag_shouldNotBeNonDismissible()
throws Exception {
// Given: a call notification has the flag FLAG_ONGOING_EVENT set
@@ -13178,8 +13261,7 @@
.build();
Notification n = new Notification.Builder(mContext, "test")
.setOngoing(true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.build();
// When: fix the notification with NotificationManagerService
@@ -14338,6 +14420,9 @@
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(3)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ actionIntent2.cancel();
+ actionIntent1.cancel();
}
@Test
@@ -14366,6 +14451,10 @@
eq(REASON_NOTIFICATION_SERVICE), any());
verify(mAmi, times(4)).setPendingIntentAllowBgActivityStarts(any(),
any(), eq(FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER));
+ contentIntent.cancel();
+ publicContentIntent.cancel();
+ actionIntent.cancel();
+ publicActionIntent.cancel();
}
@Test
@@ -14891,8 +14980,8 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
public void testFixNotification_clearsLifetimeExtendedFlag() throws Exception {
- mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
Notification n = new Notification.Builder(mContext, "test")
.setFlag(FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
.build();
@@ -15321,7 +15410,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_missingTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15333,7 +15422,7 @@
}
@Test
- @EnableFlags(Flags.FLAG_ALL_NOTIFS_NEED_TTL)
+ @EnableFlags(FLAG_ALL_NOTIFS_NEED_TTL)
public void testFixNotification_doesNotOverwriteTtl() throws Exception {
Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -15351,8 +15440,7 @@
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
.setFlag(FLAG_USER_INITIATED_JOB, true)
- .setStyle(Notification.CallStyle.forOngoingCall(
- person, mock(PendingIntent.class)))
+ .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent))
.setSmallIcon(android.R.drawable.sym_def_app_icon);
StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1,
testName, mUid, 0, nb.build(), userHandle, null, 0);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index aeeca2ae..5033a380 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -3981,7 +3981,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), anyInt())).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(eq(0), anyInt())).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
@@ -4097,7 +4097,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, a.getId(), false)
@@ -4120,7 +4120,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
@@ -4309,7 +4309,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
ArraySet<String> toRemove = new ArraySet<>();
@@ -4341,7 +4341,7 @@
pm.applicationInfo = new ApplicationInfo();
pm.applicationInfo.uid = UID_O;
List<PackageInfo> packages = ImmutableList.of(pm);
- when(mPm.getInstalledPackagesAsUser(any(), eq(0))).thenReturn(packages);
+ when(mPm.getInstalledPackagesAsUser(0, 0)).thenReturn(packages);
mHelper.updateFixedImportance(users);
assertTrue(mHelper.isImportanceLocked(PKG_O, UID_O));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index ad420f6..527001d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -55,6 +55,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.compat.IPlatformCompat;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
@@ -155,7 +156,8 @@
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
+ mUsageStats, new String[] {ImportanceExtractor.class.getName()},
+ mock(IPlatformCompat.class));
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
new file mode 100644
index 0000000..8b46c8c
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TimeToLiveHelperTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.notification;
+
+import static com.android.server.notification.TimeToLiveHelper.EXTRA_KEY;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.SuppressLint;
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.testing.TestableLooper;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
+public class TimeToLiveHelperTest extends UiServiceTestCase {
+
+ TimeToLiveHelper mHelper;
+ @Mock
+ NotificationManagerPrivate mNm;
+ @Mock
+ AlarmManager mAm;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext.addMockSystemService(AlarmManager.class, mAm);
+ mHelper = new TimeToLiveHelper(mNm, mContext);
+ }
+
+ @After
+ public void tearDown() {
+ mHelper.destroy();
+ }
+
+ private NotificationRecord getRecord(String tag, int timeoutAfter) {
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setTimeoutAfter(timeoutAfter);
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, tag, mUid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
+ @Test
+ public void testTimeout() {
+ mHelper.scheduleTimeoutLocked(getRecord("testTimeout", 1), 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+ }
+
+ @Test
+ public void testTimeoutExpires() {
+ NotificationRecord r = getRecord("testTimeoutExpires", 1);
+
+ mHelper.scheduleTimeoutLocked(r, 1);
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captor.capture());
+
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captor.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).isEmpty();
+ }
+
+ @Test
+ public void testTimeoutExpires_twoEntries() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+
+ ArgumentCaptor<PendingIntent> captorNewSet = ArgumentCaptor.forClass(PendingIntent.class);
+ mHelper.mNotificationTimeoutReceiver.onReceive(mContext, captorSet.getValue().getIntent());
+
+ assertThat(mHelper.mKeys).hasSize(1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorNewSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedSecond() {
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), any());
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+
+ @Test
+ public void testTimeout_earlierEntryAddedFirst() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(first.getKey());
+ verify(mAm, never()).cancel((PendingIntent) any());
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+ }
+
+ @Test
+ public void testTimeout_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(1);
+
+ // cancel original alarm
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // schedule later alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(4L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+ }
+
+ @Test
+ public void testTimeout_twoEntries_updateEarliestEntry() {
+ NotificationRecord first = getRecord("testTimeoutFirst", 1);
+ NotificationRecord later = getRecord("testTimeoutSecond", 2);
+
+ mHelper.scheduleTimeoutLocked(first, 1);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(2L), any());
+
+ mHelper.scheduleTimeoutLocked(later, 1);
+
+ NotificationRecord firstUpdated = getRecord("testTimeoutFirst", 3);
+ ArgumentCaptor<PendingIntent> captorSet = ArgumentCaptor.forClass(PendingIntent.class);
+ ArgumentCaptor<PendingIntent> captorCancel = ArgumentCaptor.forClass(PendingIntent.class);
+
+ mHelper.scheduleTimeoutLocked(firstUpdated, 1);
+
+ assertThat(mHelper.mKeys).hasSize(2);
+ assertThat(mHelper.mKeys.first().second).isEqualTo(later.getKey());
+
+ // "first" was canceled because it's now later
+ verify(mAm).cancel(captorCancel.capture());
+ assertThat(captorCancel.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(first.getKey());
+
+ // "later" is now the first entry, and needs the matching alarm
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq(3L), captorSet.capture());
+ assertThat(captorSet.getValue().getIntent().getStringExtra(EXTRA_KEY))
+ .isEqualTo(later.getKey());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2c88ed2..7356b43 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1717,6 +1717,7 @@
// The display should be rotated after the launch is finished.
doReturn(false).when(app).isAnimating(anyInt(), anyInt());
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
+ waitHandlerIdle(mWm.mH);
mStatusBarWindow.finishSeamlessRotation(t);
mNavBarWindow.finishSeamlessRotation(t);
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 80e169d..b90fa21 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -25,6 +25,8 @@
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 org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -37,6 +39,8 @@
import androidx.test.filters.SmallTest;
+import com.android.server.wm.testing.Assert;
+
import org.junit.Before;
import org.junit.Test;
@@ -288,4 +292,56 @@
false /* forTabletopMode */,
LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
}
+
+ @Test
+ public void test_setLetterboxHorizontalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxVerticalPositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxBookModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxBookModePositionMultiplier(1);
+ }
+
+ @Test
+ public void test_setLetterboxTabletopModePositionMultiplier_validValues() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(-1));
+ assertThrows(IllegalArgumentException.class,
+ () -> mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(2));
+
+ // Does not throw an exception for values [0,1].
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f);
+ mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 649f520..dcf3dad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -74,6 +74,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
@@ -237,6 +238,24 @@
assertFalse(wpc.hasActivities());
}
+ @Test
+ public void testAttachApplication() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.detachFromProcess();
+ mAtm.startProcessAsync(activity, false /* knownToBeDead */,
+ true /* isTop */, "test" /* hostingType */);
+ final WindowProcessController proc = mSystemServicesTestRule.addProcess(
+ activity.packageName, activity.processName,
+ 6789 /* pid */, activity.info.applicationInfo.uid);
+ try {
+ mRootWindowContainer.attachApplication(proc);
+ verify(mSupervisor).realStartActivityLocked(eq(activity), eq(proc), anyBoolean(),
+ anyBoolean());
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
/**
* This test ensures that we do not try to restore a task based off an invalid task id. We
* should expect {@code null} to be returned in this case.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index fbf1426..2e80bc7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4143,31 +4143,6 @@
}
@Test
- public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (2800, 1400).
-
- // Below 0.0.
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
-
- // Above 1.0
- assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
- /* letterboxHorizontalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
- // After the display is resized to (700, 1400).
- /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
- }
-
- @Test
public void testUpdateResolvedBoundsHorizontalPosition_right() {
// Display configured as (2800, 1400).
assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
@@ -4398,31 +4373,6 @@
}
@Test
- public void testUpdateResolvedBoundsVerticalPosition_invalidMultiplier_defaultToCenter() {
- // Display configured as (1400, 2800).
-
- // Below 0.0.
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ -1.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
-
- // Above 1.0
- assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
- /* letterboxVerticalPositionMultiplier */ 2.0f,
- // At launch.
- /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
- // After 90 degree rotation.
- /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
- // After the display is resized to (1400, 700).
- /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
- }
-
- @Test
public void testUpdateResolvedBoundsVerticalPosition_bottom() {
// Display configured as (1400, 2800).
assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 413d003..b9fe074 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -68,6 +68,7 @@
import android.os.PowerSaveState;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.DisplayInfo;
@@ -194,6 +195,7 @@
mMockitoSession = mockitoSession()
.mockStatic(LocalServices.class, spyStubOnly)
.mockStatic(DeviceConfig.class, spyStubOnly)
+ .mockStatic(UserManager.class, spyStubOnly)
.mockStatic(SurfaceControl.class, mockStubOnly)
.mockStatic(DisplayControl.class, mockStubOnly)
.mockStatic(LockGuard.class, mockStubOnly)
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 52485ee..002a3d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -19,6 +19,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1024,6 +1026,58 @@
}
@Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_systemOrganizer() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG);
+ mController.unregisterOrganizer(mIOrganizer);
+ registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation should be set for a system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
+ public void testApplyTransaction_createTaskFragment_overrideOrientation_nonSystemOrganizer() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activity.info.applicationInfo.uid = uid;
+ activity.getTask().effectiveUid = uid;
+ final IBinder fragmentToken = new Binder();
+
+ // Create a TaskFragment with OverrideOrientation set.
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken, activity.token)
+ .setOverrideOrientation(SCREEN_ORIENTATION_BEHIND)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // TaskFragment override orientation is ignored for a non-system organizer.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ assertNotNull(taskFragment);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, taskFragment.getOverrideOrientation());
+ }
+
+ @Test
public void testApplyTransaction_reparentActivityToTaskFragment_triggerLifecycleUpdate() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3c5b12c..4837fcb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -752,6 +753,21 @@
}
@Test
+ public void testGetOrientation_reportOverrideOrientation() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragmentWithActivity(task);
+ final ActivityRecord activity = tf.getTopMostActivity();
+ tf.setOverrideOrientation(SCREEN_ORIENTATION_BEHIND);
+
+ // Should report the override orientation
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+
+ // Should report the override orientation even if the activity requests a different value
+ activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_BEHIND, tf.getOrientation(SCREEN_ORIENTATION_UNSET));
+ }
+
+ @Test
public void testUpdateImeParentForActivityEmbedding() {
// Setup two activities in ActivityEmbedding.
final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 50db99e..5fe71a1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -25,7 +25,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING;
@@ -38,6 +37,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -82,6 +82,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -1304,7 +1305,8 @@
}
@Test
- public void testAddOverlayWindowToUnassignedDisplay_notAllowed() {
+ public void testAddOverlayWindowToUnassignedDisplay_notAllowed_ForVisibleBackgroundUsers() {
+ doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
int uid = 100000; // uid for non-system user
Session session = createTestSession(mAtm, 1234 /* pid */, uid);
DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 43b424f..69b5c37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1681,7 +1681,8 @@
WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
clearInvocations(mWm.mAtmService.mRootWindowContainer);
// The token for the PIP root task may have changed when the task entered PIP mode, so do
@@ -1690,7 +1691,8 @@
record.getRootTask().mRemoteToken.toWindowContainerToken();
t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+ verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+ any(), any(), anyBoolean());
}
@Test
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 0a8a18dc..f076de3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -289,7 +289,7 @@
@SystemApi
public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
- /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+ /** The otaspMode passed to SercvieState changes */
/** @hide */
static public final int OTASP_UNINITIALIZED = 0;
/** @hide */
@@ -995,9 +995,9 @@
"android.intent.action.CALL_DISCONNECT_CAUSE";
/**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the disconnect cause.
+ * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+ * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+ * an integer containing the disconnect cause.
*
* @see DisconnectCause
*
@@ -1012,9 +1012,9 @@
public static final String EXTRA_DISCONNECT_CAUSE = "disconnect_cause";
/**
- * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and
- * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer
- * containing the disconnect cause provided by the RIL.
+ * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and {@link
+ * TelephonyCallback.PreciseCallStateListener#onPreciseCallStateChanged(PreciseCallState)} for
+ * an integer containing the disconnect cause provided by the RIL.
*
* @see PreciseDisconnectCause
*
@@ -6437,8 +6437,8 @@
* This method considers not only calls in the Telephony stack, but also calls via other
* {@link android.telecom.ConnectionService} implementations.
* <p>
- * Note: The call state returned via this method may differ from what is reported by
- * {@link PhoneStateListener#onCallStateChanged(int, String)}, as that callback only considers
+ * Note: The call state returned via this method may differ from what is reported by {@link
+ * TelephonyCallback.CallStateListener#onCallStateChanged(int)}, as that callback only considers
* Telephony (mobile) calls.
* <p>
* Requires Permission:
@@ -6996,7 +6996,7 @@
*
* <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
* if this API results in a change of the cached CellInfo, that change will be reported via
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}.
*
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
* longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
@@ -7109,7 +7109,7 @@
* camped/registered, serving, and neighboring cells.
*
* <p>Any available results from this request will be provided by calls to
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
* for each active subscription.
*
* <p>This method returns valid data for devices with
@@ -7172,7 +7172,7 @@
* camped/registered, serving, and neighboring cells.
*
* <p>Any available results from this request will be provided by calls to
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * {@link TelephonyCallback.CellInfoListener#onCellInfoChanged(List) onCellInfoChanged()}
* for each active subscription.
*
* <p>This method returns valid data for devices with
@@ -7248,8 +7248,8 @@
}
/**
- * Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
- * PhoneStateListener.onCellInfoChanged} will be invoked.
+ * Sets the minimum time in milli-seconds between {@link
+ * TelephonyCallback.CellInfoListener#onCellInfoChanged(List)} will be invoked.
*<p>
* The default, 0, means invoke onCellInfoChanged when any of the reported
* information changes. Setting the value to INT_MAX(0x7fffffff) means never issue
@@ -11364,7 +11364,7 @@
* Shut down all the live radios over all the slot indexes.
*
* <p>To know when the radio has completed powering off, use
- * {@link PhoneStateListener#LISTEN_SERVICE_STATE LISTEN_SERVICE_STATE}.
+ * {@link TelephonyCallback.ServiceStateListener}.
*
* @throws UnsupportedOperationException If the device does not have
* {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
@@ -13058,8 +13058,9 @@
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
*
- * If you want continuous updates of service state info, register a {@link PhoneStateListener}
- * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+ * #registerTelephonyCallback}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
@@ -13086,8 +13087,9 @@
* <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
* given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
*
- * If you want continuous updates of service state info, register a {@link PhoneStateListener}
- * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+ * If you want continuous updates of service state info, register a {@link TelephonyCallback}
+ * that implements {@link TelephonyCallback.ServiceStateListener} through {@link
+ * #registerTelephonyCallback}.
*
* There's another way to renounce permissions with a custom context
* {@code AttributionSource.Builder#setRenouncedPermissions(Set<String>)} but only for system
@@ -17892,9 +17894,9 @@
* measurements breach the specified thresholds.
*
* To be notified, set the signal strength update request and then register
- * {@link TelephonyManager#listen(PhoneStateListener, int)} with
- * {@link PhoneStateListener#LISTEN_SIGNAL_STRENGTHS}. The notification will arrive through
- * {@link PhoneStateListener#onSignalStrengthsChanged(SignalStrength)}.
+ * {@link TelephonyCallback} that implements {@link TelephonyCallback.SignalStrengthsListener}
+ * through {@link #registerTelephonyCallback}. The notification will arrive through
+ * {@link TelephonyCallback.SignalStrengthsListener#onSignalStrengthsChanged(SignalStrength)}.
*
* To stop receiving the notification over the specified thresholds, pass the same
* {@link SignalStrengthUpdateRequest} object to
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index f6f766a..8d2b927 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,17 +19,26 @@
import android.content.Context
import android.content.ContextWrapper
+import android.hardware.display.DisplayManager
import android.hardware.display.DisplayViewport
+import android.hardware.display.VirtualDisplay
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
+import android.os.InputEventInjectionSync
+import android.os.SystemClock
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
-import android.test.mock.MockContentResolver
+import android.view.View.OnKeyListener
import android.view.Display
+import android.view.InputDevice
+import android.view.KeyEvent
import android.view.PointerIcon
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+import android.test.mock.MockContentResolver
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.util.test.FakeSettingsProvider
import com.google.common.truth.Truth.assertThat
@@ -48,6 +57,7 @@
import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
@@ -412,6 +422,174 @@
verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f)
thread.join(100 /*millis*/)
}
+
+ private fun createVirtualDisplays(count: Int): List<VirtualDisplay> {
+ val displayManager: DisplayManager = context.getSystemService(
+ DisplayManager::class.java
+ ) as DisplayManager
+ val virtualDisplays = mutableListOf<VirtualDisplay>()
+ for (i in 0 until count) {
+ virtualDisplays.add(displayManager.createVirtualDisplay(
+ /* displayName= */ "testVirtualDisplay$i",
+ /* width= */ 100,
+ /* height= */ 100,
+ /* densityDpi= */ 100,
+ /* surface= */ null,
+ /* flags= */ 0
+ ))
+ }
+ return virtualDisplays
+ }
+
+ // Helper function that creates a KeyEvent with Keycode A with the given action
+ private fun createKeycodeAEvent(inputDevice: InputDevice, action: Int): KeyEvent {
+ val eventTime = SystemClock.uptimeMillis()
+ return KeyEvent(
+ /* downTime= */ eventTime,
+ /* eventTime= */ eventTime,
+ /* action= */ action,
+ /* code= */ KeyEvent.KEYCODE_A,
+ /* repeat= */ 0,
+ /* metaState= */ 0,
+ /* deviceId= */ inputDevice.id,
+ /* scanCode= */ 0,
+ /* flags= */ KeyEvent.FLAG_FROM_SYSTEM,
+ /* source= */ InputDevice.SOURCE_KEYBOARD
+ )
+ }
+
+ private fun createInputDevice(): InputDevice {
+ return InputDevice.Builder()
+ .setId(123)
+ .setName("abc")
+ .setDescriptor("def")
+ .setSources(InputDevice.SOURCE_KEYBOARD)
+ .build()
+ }
+
+ @Test
+ fun addUniqueIdAssociationByDescriptor_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.
+ // Then remove associations, then associate the InputDevice with display 2, then send
+ // and verify commands.
+
+ // Make 2 virtual displays with some mock SurfaceViews
+ val mockSurfaceView1 = mock(SurfaceView::class.java)
+ val mockSurfaceView2 = mock(SurfaceView::class.java)
+ val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+ val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+ val virtualDisplays = createVirtualDisplays(2)
+
+ // Simulate an InputDevice
+ val inputDevice = createInputDevice()
+
+ // Associate input device with display
+ service.addUniqueIdAssociationByDescriptor(
+ inputDevice.descriptor,
+ virtualDisplays[0].display.displayId.toString()
+ )
+
+ // Simulate 2 different KeyEvents
+ val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+ val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+ // Create a mock OnKeyListener object
+ val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+ // Verify that the event went to Display 1 not Display 2
+ service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+ // Call the onKey method on the mock OnKeyListener object
+ mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+ mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+ // Verify that the onKey method was called with the expected arguments
+ verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+ // Remove association
+ service.removeUniqueIdAssociationByDescriptor(inputDevice.descriptor)
+
+ // Associate with Display 2
+ service.addUniqueIdAssociationByDescriptor(
+ inputDevice.descriptor,
+ virtualDisplays[1].display.displayId.toString()
+ )
+
+ // Simulate a KeyEvent
+ service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+ // Verify that the event went to Display 2 not Display 1
+ verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+ }
+
+ @Test
+ 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.
+ // Then remove associations, then associate the InputDevice with display 2, then send
+ // and verify commands.
+
+ // Make 2 virtual displays with some mock SurfaceViews
+ val mockSurfaceView1 = mock(SurfaceView::class.java)
+ val mockSurfaceView2 = mock(SurfaceView::class.java)
+ val mockSurfaceHolder1 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView1.holder).thenReturn(mockSurfaceHolder1)
+ val mockSurfaceHolder2 = mock(SurfaceHolder::class.java)
+ `when`(mockSurfaceView2.holder).thenReturn(mockSurfaceHolder2)
+
+ val virtualDisplays = createVirtualDisplays(2)
+
+ // Simulate an InputDevice
+ val inputDevice = createInputDevice()
+
+ // Associate input device with display
+ service.addUniqueIdAssociationByPort(
+ inputDevice.name,
+ virtualDisplays[0].display.displayId.toString()
+ )
+
+ // Simulate 2 different KeyEvents
+ val downEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_DOWN)
+ val upEvent = createKeycodeAEvent(inputDevice, KeyEvent.ACTION_UP)
+
+ // Create a mock OnKeyListener object
+ val mockOnKeyListener = mock(OnKeyListener::class.java)
+
+ // Verify that the event went to Display 1 not Display 2
+ service.injectInputEvent(downEvent, InputEventInjectionSync.NONE)
+
+ // Call the onKey method on the mock OnKeyListener object
+ mockOnKeyListener.onKey(mockSurfaceView1, /* keyCode= */ KeyEvent.KEYCODE_A, downEvent)
+ mockOnKeyListener.onKey(mockSurfaceView2, /* keyCode= */ KeyEvent.KEYCODE_A, upEvent)
+
+ // Verify that the onKey method was called with the expected arguments
+ verify(mockOnKeyListener).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, downEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent)
+
+ // Remove association
+ service.removeUniqueIdAssociationByPort(inputDevice.name)
+
+ // Associate with Display 2
+ service.addUniqueIdAssociationByPort(
+ inputDevice.name,
+ virtualDisplays[1].display.displayId.toString()
+ )
+
+ // Simulate a KeyEvent
+ service.injectInputEvent(upEvent, InputEventInjectionSync.NONE)
+
+ // Verify that the event went to Display 2 not Display 1
+ verify(mockOnKeyListener).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, upEvent)
+ verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent)
+ }
}
private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c73177..a43cd39 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@
platform_apis: true,
certificate: "platform",
libs: ["org.apache.http.legacy"],
+ optional_uses_libs: ["org.apache.http.legacy"],
optimize: {
enabled: false,
},
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index fdf8fb8..c8b60e5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -17,9 +17,11 @@
package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
-import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
@@ -44,6 +46,7 @@
import android.os.OutcomeReceiver;
import android.os.PowerManager;
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
@@ -65,6 +68,7 @@
private static final int REPLAY_BITMAP_LEN_BYTE = 512;
private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
@Mock private IpSecTransformWrapper mIpSecTransform;
@@ -91,6 +95,9 @@
eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
anyInt()))
.thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
@@ -112,6 +119,20 @@
.build();
}
+ private static IpSecTransformState newNextTransformState(
+ IpSecTransformState before,
+ long timeDiffMillis,
+ long rxSeqNoDiff,
+ long packtCountDiff,
+ int packetInWin) {
+ return new IpSecTransformState.Builder()
+ .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis)
+ .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff)
+ .setPacketCount(before.getPacketCount() + packtCountDiff)
+ .setReplayBitmap(newReplayBitmap(packetInWin))
+ .build();
+ }
+
private static byte[] newReplayBitmap(int receivedPktCnt) {
final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
for (int i = 0; i < receivedPktCnt; i++) {
@@ -165,7 +186,7 @@
// Verify the first polled state is stored
assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Verify next poll is scheduled
assertNull(mTestLooper.nextMessage());
@@ -278,7 +299,7 @@
xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
@Test
@@ -289,17 +310,19 @@
xfrmStateReceiver.onError(new RuntimeException("Test"));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
private void checkHandleLossRate(
- int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+ PacketLossCalculationResult mockPacketLossRate,
+ boolean isLastStateExpectedToUpdate,
+ boolean isCallbackExpected)
throws Exception {
final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
startMonitorAndCaptureStateReceiver();
doReturn(mockPacketLossRate)
.when(mPacketLossCalculator)
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
@@ -309,7 +332,10 @@
// Verifications
verify(mPacketLossCalculator)
.getPacketLossRatePercentage(
- eq(mTransformStateInitial), eq(transformNew), anyString());
+ eq(mTransformStateInitial),
+ eq(transformNew),
+ eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED),
+ anyString());
if (isLastStateExpectedToUpdate) {
assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
@@ -327,30 +353,53 @@
@Test
public void testHandleLossRate_validationPass() throws Exception {
checkHandleLossRate(
- 2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
}
@Test
public void testHandleLossRate_validationFail() throws Exception {
checkHandleLossRate(
- 22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(22),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false);
}
@Test
public void testHandleLossRate_resultUnavalaible() throws Exception {
checkHandleLossRate(
- PACKET_LOSS_UNAVALAIBLE,
+ PacketLossCalculationResult.invalid(),
false /* isLastStateExpectedToUpdate */,
false /* isCallbackExpected */);
}
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(22),
+ true /* isLastStateExpectedToUpdate */,
+ false /* isCallbackExpected */);
+ }
+
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
+ }
+
private void checkGetPacketLossRate(
- IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+ IpSecTransformState oldState,
+ IpSecTransformState newState,
+ PacketLossCalculationResult expectedLossRate)
throws Exception {
assertEquals(
expectedLossRate,
- mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG));
}
private void checkGetPacketLossRate(
@@ -362,14 +411,45 @@
throws Exception {
final IpSecTransformState newState =
newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+ checkGetPacketLossRate(
+ oldState, newState, PacketLossCalculationResult.valid(expectedDataLossRate));
+ }
+
+ private void checkGetPacketLossRate(
+ IpSecTransformState oldState,
+ int rxSeqNo,
+ int packetCount,
+ int packetInWin,
+ PacketLossCalculationResult expectedDataLossRate)
+ throws Exception {
+ final IpSecTransformState newState =
+ newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
}
@Test
public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
checkGetPacketLossRate(
- mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
- checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+ mTransformStateInitial,
+ mTransformStateInitial,
+ PacketLossCalculationResult.invalid());
+ checkGetPacketLossRate(
+ mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
+ }
+
+ @Test
+ public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception {
+ final int oldRxNo = 4096;
+ final int oldPktCnt = 4096;
+ final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1;
+ final byte[] bitmapReceiveAll = newReplayBitmap(4096);
+
+ final IpSecTransformState oldState =
+ newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll);
+ final IpSecTransformState newState =
+ newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll);
+
+ checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid());
}
@Test
@@ -419,6 +499,45 @@
checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
}
+ private void checkGetPktLossRate_unusualSeqNumLeap(
+ int maxSeqNumIncreasePerSecond,
+ int timeDiffMillis,
+ int rxSeqNoDiff,
+ PacketLossCalculationResult expected)
+ throws Exception {
+ final IpSecTransformState oldState = mTransformStateInitial;
+ final IpSecTransformState newState =
+ newNextTransformState(
+ oldState,
+ timeDiffMillis,
+ rxSeqNoDiff,
+ 1 /* packtCountDiff */,
+ 1 /* packetInWin */);
+
+ assertEquals(
+ expected,
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, maxSeqNumIncreasePerSecond, TAG));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 30000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.unusualSeqNumLeap(100));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 5000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.valid(100));
+ }
+
// Verify the polling event is scheduled with expected delays
private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
if (expectedDelayMs > 0) {
@@ -445,4 +564,24 @@
// Verify the 3rd poll is scheduled with configured delay
verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
}
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond() throws Exception {
+ final int seqNumLeapNegative = 500_000;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception {
+ final int seqNumLeapNegative = -10;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
+ getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index af6daa1..6189fb0 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
when(mNetwork.getNetId()).thenReturn(-1);