Merge "chore(magnification settings): add logging for settings panel ui interactions" into udc-dev
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d8f693c..fcff581 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -81,8 +81,6 @@
name: "platformprotos",
srcs: [
":ipconnectivity-proto-src",
- ":libstats_atom_enum_protos",
- ":libstats_atom_message_protos",
":libstats_internal_protos",
":statsd_internal_protos",
"cmds/am/proto/instrumentation_data.proto",
diff --git a/TestProtoLibraries.bp b/TestProtoLibraries.bp
index 9e2a64c..2d87841 100644
--- a/TestProtoLibraries.bp
+++ b/TestProtoLibraries.bp
@@ -15,8 +15,6 @@
java_library_host {
name: "platformtestprotos",
srcs: [
- ":libstats_atom_enum_protos",
- ":libstats_atom_message_protos",
":libstats_internal_protos",
":statsd_internal_protos",
],
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 59b5978..783e7d3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8359,6 +8359,26 @@
* from Android {@link android.os.Build.VERSION_CODES#R}, requests to disable camera from
* legacy device admins targeting SDK version {@link android.os.Build.VERSION_CODES#P} or
* below will be silently ignored.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the camera disabled
+ * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier returned from
+ * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)} with user restriction
+ * {@link UserManager#DISALLOW_CAMERA}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with or null if
the caller is not a device admin
@@ -9783,6 +9803,27 @@
* <p>
* The calling device admin must be a profile owner or device owner. If it is not, a security
* exception will be thrown.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the persistent preferred
+ * activity policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#PERSISTENT_PREFERRED_ACTIVITY_POLICY}
+ * <li> The additional policy params bundle, which contains
+ * {@link PolicyUpdateReceiver#EXTRA_INTENT_FILTER} the intent filter the policy applies to
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* <p>NOTE: Performs disk I/O and shouldn't be called on the main thread.
*
@@ -9816,6 +9857,27 @@
* <p>
* The calling device admin must be a profile owner. If it is not, a security exception will be
* thrown.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the persistent preferred
+ * activity policy has been cleared, {@link PolicyUpdateReceiver#onPolicySetResult(Context,
+ * String, Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy
+ * was successfully cleared or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#PERSISTENT_PREFERRED_ACTIVITY_POLICY}
+ * <li> The additional policy params bundle, which contains
+ * {@link PolicyUpdateReceiver#EXTRA_INTENT_FILTER} the intent filter the policy applies to
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully cleared or the
+ * reason the policy failed to be cleared
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -11475,6 +11537,26 @@
* {@link #getParentProfileInstance(ComponentName)}. To set a restriction globally, call
* {@link #addUserRestrictionGlobally} instead.
*
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction
+ * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier returned from
+ * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner and if the caller
@@ -11507,6 +11589,25 @@
* <p> See the constants in {@link android.os.UserManager} for the list of restrictions that can
* be enforced device-wide. These constants will also state in their documentation which
* permission is required to manage the restriction using this API.
+ * <p>
+ * After the user restriction policy has been set,
+ * {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not.
+ * This callback will contain:
+ * <ul>
+ * <li> The policy identifier returned from
+ * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner and if the
@@ -11544,6 +11645,26 @@
* above, calling this API will result in clearing any local and global restriction with the
* specified key that was previously set by the caller.
*
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user restriction
+ * policy has been cleared, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully cleared or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier returned from
+ * {@link DevicePolicyIdentifiers#getIdentifierForUserRestriction(String)}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully cleared or the
+ * reason the policy failed to be cleared
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param key The key of the restriction.
* @throws SecurityException if {@code admin} is not a device or profile owner and if the
@@ -11692,6 +11813,27 @@
* {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
* of an organization-owned managed profile and the package must be a system package. If called
* on the parent instance, then the package is hidden or unhidden in the personal profile.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the application hidden
+ * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#APPLICATION_HIDDEN_POLICY}
+ * <li> The additional policy params bundle, which contains
+ * {@link PolicyUpdateReceiver#EXTRA_PACKAGE_NAME} the package name the policy applies to
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if the caller is not a device admin.
@@ -11731,6 +11873,9 @@
* of an organization-owned managed profile and the package must be a system package. If called
* on the parent instance, this will determine whether the package is hidden or unhidden in the
* personal profile.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the returned policy will be the
+ * current resolved policy rather than the policy set by the calling admin.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
* {@code null} if the caller is not a device admin.
@@ -11855,6 +12000,27 @@
* {@link #getParentProfileInstance(ComponentName)} by the profile owner on an
* organization-owned device, to restrict accounts that may not be managed on the primary
* profile.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the account management
+ * disabled policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#ACCOUNT_MANAGEMENT_DISABLED_POLICY}
+ * <li> The additional policy params bundle, which contains
+ * {@link PolicyUpdateReceiver#EXTRA_ACCOUNT_TYPE} the account type the policy applies to
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -11987,6 +12153,28 @@
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. See
* {@link #isAffiliatedUser}.
* Any package set via this method will be cleared if the user becomes unaffiliated.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the lock task policy has
+ * been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin on whether the policy was successfully set or not.
+ * This callback will contain:
+ * <ul>
+ * <li> The policy identifier {@link DevicePolicyIdentifiers#LOCK_TASK_POLICY}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, lock task features and lock task
+ * packages are bundled as one policy. A failure to apply one will result in a failure to apply
+ * the other.
*
* @param packages The list of packages allowed to enter lock task mode
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
@@ -12016,6 +12204,9 @@
/**
* Returns the list of packages allowed to start the lock task mode.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the returned policy will be the
+ * current resolved policy rather than the policy set by the calling admin.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -12068,6 +12259,28 @@
* permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_TASK}. See
* {@link #isAffiliatedUser}.
* Any features set using this method are cleared if the user becomes unaffiliated.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the lock task features
+ * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, Bundle,
+ * TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier {@link DevicePolicyIdentifiers#LOCK_TASK_POLICY}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, lock task features and lock task
+ * packages are bundled as one policy. A failure to apply one will result in a failure to apply
+ * the other.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -12092,6 +12305,9 @@
/**
* Gets which system features are enabled for LockTask mode.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the returned policy will be the
+ * current resolved policy rather than the policy set by the calling admin.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -12577,6 +12793,27 @@
* profile owner, or by a delegate given the {@link #DELEGATION_BLOCK_UNINSTALL} scope via
* {@link #setDelegatedScopes} or holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the set uninstall blocked
+ * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#PACKAGE_UNINSTALL_BLOCKED_POLICY}
+ * <li> The additional policy params bundle, which contains
+ * {@link PolicyUpdateReceiver#EXTRA_PACKAGE_NAME} the package name the policy applies to
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -12614,6 +12851,9 @@
* <strong>Note:</strong> If your app targets Android 11 (API level 30) or higher,
* this method returns a filtered result. Learn more about how to
* <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the returned policy will be the
+ * current resolved policy rather than the policy set by the calling admin.
*
* @param admin The name of the admin component whose blocking policy will be checked, or
* {@code null} to check whether any admin has blocked the uninstallation. Starting
@@ -15495,7 +15735,7 @@
throwIfParentInstance("getAllCrossProfilePackages");
if (mService != null) {
try {
- return new ArraySet<>(mService.getAllCrossProfilePackages());
+ return new ArraySet<>(mService.getAllCrossProfilePackages(mContext.getUserId()));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -15723,6 +15963,25 @@
* control over apps. User will not be able to clear app data or force-stop packages. When
* called by a device owner, applies to all users on the device. Packages with user control
* disabled are exempted from App Standby Buckets.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, after the user control disabled
+ * packages policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String,
+ * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was
+ * successfully set or not. This callback will contain:
+ * <ul>
+ * <li> The policy identifier
+ * {@link DevicePolicyIdentifiers#USER_CONTROL_DISABLED_PACKAGES_POLICY}
+ * <li> The {@link TargetUser} that this policy relates to
+ * <li> The {@link PolicyUpdateResult}, which will be
+ * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the
+ * reason the policy failed to be set
+ * (e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY})
+ * </ul>
+ * If there has been a change to the policy,
+ * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser,
+ * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the
+ * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult}
+ * will contain the reason why the policy changed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
@@ -15749,6 +16008,9 @@
* Returns the list of packages over which user control is disabled by a device or profile
* owner or holders of the permission
* {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}.
+ * <p>
+ * Starting from {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the returned policy will be the
+ * current resolved policy rather than the policy set by the calling admin.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
* caller is not a device admin.
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 5345947..d85b2cd 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -206,7 +206,7 @@
*
* @hide
*/
- public abstract List<String> getAllCrossProfilePackages();
+ public abstract List<String> getAllCrossProfilePackages(int userId);
/**
* Returns the default package names set by the OEM that are allowed to communicate
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9795cab..003e804 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -515,7 +515,7 @@
void setCrossProfilePackages(in ComponentName admin, in List<String> packageNames);
List<String> getCrossProfilePackages(in ComponentName admin);
- List<String> getAllCrossProfilePackages();
+ List<String> getAllCrossProfilePackages(int userId);
List<String> getDefaultCrossProfilePackages();
boolean isManagedKiosk();
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index af5c6dd..2e67225 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1331,10 +1331,12 @@
/**
* Enable or disable secure transport for testing. Defaults to enabled.
+ * Should not be used outside of testing.
*
* @param enabled true to enable. false to disable.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void enableSecureTransport(boolean enabled) {
try {
mService.enableSecureTransport(enabled);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 379a011..2200af6 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -892,8 +892,9 @@
*/
private boolean isContentRedirectionAllowedForUser(int incomingUserId) {
if (MediaStore.AUTHORITY.equals(mAuthority)) {
- if (mUsersRedirectedToOwnerForMedia.indexOfKey(incomingUserId) >= 0) {
- return mUsersRedirectedToOwnerForMedia.valueAt(incomingUserId);
+ int incomingUserIdIndex = mUsersRedirectedToOwnerForMedia.indexOfKey(incomingUserId);
+ if (incomingUserIdIndex >= 0) {
+ return mUsersRedirectedToOwnerForMedia.valueAt(incomingUserIdIndex);
}
// Haven't seen this user yet, look it up
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 307f306..e763e95 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6907,7 +6907,7 @@
*
* @hide
*/
- public static final int FLAG_IGNORE_EPHEMERAL = 0x00000200;
+ public static final int FLAG_IGNORE_EPHEMERAL = 0x80000000;
/**
* If set, the new activity is not kept in the history stack. As soon as
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index b5d2f2c..036a4eb 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1698,27 +1698,6 @@
}
/**
- * Returns whether the activity supports size changes.
- * @hide
- */
- @SizeChangesSupportMode
- public int supportsSizeChanges() {
- if (isChangeEnabled(FORCE_NON_RESIZE_APP)) {
- return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
- }
-
- if (supportsSizeChanges) {
- return SIZE_CHANGES_SUPPORTED_METADATA;
- }
-
- if (isChangeEnabled(FORCE_RESIZE_APP)) {
- return SIZE_CHANGES_SUPPORTED_OVERRIDE;
- }
-
- return SIZE_CHANGES_UNSUPPORTED_METADATA;
- }
-
- /**
* Returns if the activity should never be sandboxed to the activity window bounds.
* @hide
*/
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 0993160..3e72d81 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -21,7 +21,14 @@
/** @hide */
interface IDeviceStateManager {
- /** Returns the current device state info. */
+ /**
+ * Returns the current device state info. This {@link DeviceStateInfo} object will always
+ * include the list of supported states. If there has been no base state or committed state
+ * provided yet to the system server, this {@link DeviceStateInfo} object will include
+ * {@link DeviceStateManager#INVALID_DEVICE_STATE} for each respectively.
+ *
+ * This method should not be used to notify callback clients.
+ */
DeviceStateInfo getDeviceStateInfo();
/**
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index 6b7d8c3..7ae88b8 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -22,6 +22,7 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -233,6 +234,31 @@
dest.writeLong(colorSampleDuration);
}
+ @Override
+ public String toString() {
+ return "BrightnessChangeEvent{"
+ + "brightness: " + brightness
+ + ", timeStamp: " + timeStamp
+ + ", packageName: " + packageName
+ + ", userId: " + userId
+ + ", uniqueDisplayId: " + uniqueDisplayId
+ + ", luxValues: " + Arrays.toString(luxValues)
+ + ", luxTimestamps: " + Arrays.toString(luxTimestamps)
+ + ", batteryLevel: " + batteryLevel
+ + ", powerBrightnessFactor: " + powerBrightnessFactor
+ + ", nightMode: " + nightMode
+ + ", colorTemperature: " + colorTemperature
+ + ", reduceBrightColors: " + reduceBrightColors
+ + ", reduceBrightColorsStrength: " + reduceBrightColorsStrength
+ + ", reduceBrightColorsOffset: " + reduceBrightColorsOffset
+ + ", lastBrightness: " + lastBrightness
+ + ", isDefaultBrightnessConfig: " + isDefaultBrightnessConfig
+ + ", isUserSetBrightness: " + isUserSetBrightness
+ + ", colorValueBuckets: " + Arrays.toString(colorValueBuckets)
+ + ", colorSampleDuration: " + colorSampleDuration
+ + "}";
+ }
+
/** @hide */
public static class Builder {
private float mBrightness;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index fd5e206..5c79f69 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3036,7 +3036,9 @@
public void destroy() {
try {
- if (!mArray.isClosed()) {
+ // If this process is the system server process, mArray is the same object as
+ // the memory int array kept inside SettingsProvider, so skipping the close()
+ if (!Settings.isInSystemServer() && !mArray.isClosed()) {
mArray.close();
}
} catch (IOException e) {
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 5f7486a..d04ff8e 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -123,47 +123,50 @@
*/
public static final int PICK_REASON_UNKNOWN = 0;
/**
- * This dataset is picked because of autofill provider detection was chosen.
+ * This dataset is picked because pcc wasn't enabled.
* @hide
*/
- public static final int PICK_REASON_AUTOFILL_PROVIDER_DETECTION = 1;
+ public static final int PICK_REASON_NO_PCC = 1;
+ /**
+ * This dataset is picked because provider gave this dataset.
+ * @hide
+ */
+ public static final int PICK_REASON_PROVIDER_DETECTION_ONLY = 2;
+ /**
+ * This dataset is picked because provider detection was preferred. However, provider also made
+ * this dataset available for PCC detected types, so they could've been picked up by PCC
+ * detection. This however doesn't imply that this dataset would've been chosen for sure. For
+ * eg, if PCC Detection was preferred, and PCC detected other field types, which wasn't
+ * applicable to this dataset, it wouldn't have been shown.
+ * @hide
+ */
+ public static final int PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC = 3;
/**
* This dataset is picked because of PCC detection was chosen.
* @hide
*/
- public static final int PICK_REASON_PCC_DETECTION = 2;
+ public static final int PICK_REASON_PCC_DETECTION_ONLY = 4;
/**
- * This dataset is picked because of Framework detection was chosen.
+ * This dataset is picked because of PCC Detection was preferred. However, Provider also gave
+ * this dataset, so if PCC wasn't enabled, this dataset would've been eligible anyway.
* @hide
*/
- public static final int PICK_REASON_FRAMEWORK_DETECTION = 3;
- /**
- * This dataset is picked because of Autofill Provider being a fallback.
- * @hide
- */
- public static final int PICK_REASON_AUTOFILL_PROVIDER_FALLBACK = 4;
- /**
- * This dataset is picked because of PCC detection being a fallback.
- * @hide
- */
- public static final int PICK_REASON_PCC_DETECTION_FALLBACK = 5;
- /**
- * This dataset is picked because of Framework detection being a fallback.
- * @hide
- */
- public static final int PICK_REASON_FRAMEWORK_FALLBACK = 6;
+ public static final int PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER = 5;
+ /**
+ * Reason why the dataset was eligible for autofill.
+ * @hide
+ */
@IntDef(prefix = { "PICK_REASON_" }, value = {
PICK_REASON_UNKNOWN,
- PICK_REASON_AUTOFILL_PROVIDER_DETECTION,
- PICK_REASON_PCC_DETECTION,
- PICK_REASON_FRAMEWORK_DETECTION,
- PICK_REASON_AUTOFILL_PROVIDER_FALLBACK,
- PICK_REASON_PCC_DETECTION_FALLBACK,
- PICK_REASON_FRAMEWORK_FALLBACK,
+ PICK_REASON_NO_PCC,
+ PICK_REASON_PROVIDER_DETECTION_ONLY,
+ PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC,
+ PICK_REASON_PCC_DETECTION_ONLY,
+ PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER,
})
@Retention(RetentionPolicy.SOURCE)
- @interface DatasetEligibleReason{}
+ public @interface DatasetEligibleReason{}
private @DatasetEligibleReason int mEligibleReason;
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 1850462..e95ba79 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1216,6 +1216,37 @@
"android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
/**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that the app should be opted-out from the
+ * compatibility overrides that change the resizability of the app.
+ *
+ * <p>When these compat overrides are enabled they force the packages they are applied to to be
+ * resizable / unresizable. If the app is forced to be resizable this won't change whether
+ * the app can be put into multi-windowing mode, but allow the app to resize without going into
+ * size-compat mode when the window container resizes, such as display size change or screen
+ * rotation.
+ *
+ * <p>Setting this property to {@code false} informs the system that the app must be
+ * opted-out from the compatibility treatment even if the device manufacturer has opted the app
+ * into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * <application>
+ * <property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/280052089): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES =
+ "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index b67969e..ba54686 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -275,6 +275,14 @@
public static final boolean DEFAULT_AUTOFILL_PCC_CLASSIFICATION_ENABLED = false;
// END AUTOFILL PCC CLASSIFICATION FLAGS DEFAULTS
+ // AUTOFILL FOR ALL APPS DEFAULTS
+ private static final boolean DEFAULT_AFAA_ON_UNIMPORTANT_VIEW_ENABLED = true;
+ private static final boolean DEFAULT_AFAA_ON_IMPORTANT_VIEW_ENABLED = true;
+ private static final String DEFAULT_AFAA_DENYLIST = "";
+ private static final String DEFAULT_AFAA_ALLOWLIST = "";
+ private static final String DEFAULT_AFAA_NON_AUTOFILLABLE_IME_ACTIONS = "2,3,4";
+ private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES = true;
+ private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_MULTILINE_FILTER = true;
private AutofillFeatureFlags() {};
@@ -330,7 +338,8 @@
public static boolean isTriggerFillRequestOnUnimportantViewEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW, false);
+ DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW,
+ DEFAULT_AFAA_ON_UNIMPORTANT_VIEW_ENABLED);
}
/**
@@ -341,7 +350,8 @@
public static boolean isTriggerFillRequestOnFilteredImportantViewsEnabled() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_FILTERED_IMPORTANT_VIEWS, false);
+ DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_FILTERED_IMPORTANT_VIEWS,
+ DEFAULT_AFAA_ON_IMPORTANT_VIEW_ENABLED);
}
/**
@@ -352,7 +362,8 @@
public static boolean shouldEnableAutofillOnAllViewTypes(){
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES, false);
+ DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES,
+ DEFAULT_AFAA_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES);
}
/**
@@ -363,7 +374,9 @@
*/
public static Set<String> getNonAutofillableImeActionIdSetFromFlag() {
final String mNonAutofillableImeActions = DeviceConfig.getString(
- DeviceConfig.NAMESPACE_AUTOFILL, DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS, "");
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_NON_AUTOFILLABLE_IME_ACTION_IDS,
+ DEFAULT_AFAA_NON_AUTOFILLABLE_IME_ACTIONS);
return new ArraySet<>(Arrays.asList(mNonAutofillableImeActions.split(",")));
}
@@ -378,7 +391,8 @@
public static String getDenylistStringFromFlag() {
return DeviceConfig.getString(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
+ DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW,
+ DEFAULT_AFAA_DENYLIST);
}
/**
@@ -389,7 +403,8 @@
public static String getAllowlistStringFromFlag() {
return DeviceConfig.getString(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST, "");
+ DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST,
+ DEFAULT_AFAA_ALLOWLIST);
}
/**
* Whether include all views that have autofill type not none in assist structure.
@@ -422,7 +437,8 @@
public static boolean shouldEnableMultilineFilter() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_MULTILINE_FILTER_ENABLED, false);
+ DEVICE_CONFIG_MULTILINE_FILTER_ENABLED,
+ DEFAULT_AFAA_SHOULD_ENABLE_MULTILINE_FILTER);
}
// START AUTOFILL PCC CLASSIFICATION FUNCTIONS
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 2ef7419..ea75076 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -468,7 +468,8 @@
COMMIT_REASON_ACTIVITY_FINISHED,
COMMIT_REASON_VIEW_COMMITTED,
COMMIT_REASON_VIEW_CLICKED,
- COMMIT_REASON_VIEW_CHANGED
+ COMMIT_REASON_VIEW_CHANGED,
+ COMMIT_REASON_SESSION_DESTROYED
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutofillCommitReason {}
@@ -507,6 +508,12 @@
* @hide
*/
public static final int COMMIT_REASON_VIEW_CHANGED = 4;
+ /**
+ * Autofill context was committed because of the session was destroyed.
+ *
+ * @hide
+ */
+ public static final int COMMIT_REASON_SESSION_DESTROYED = 5;
/**
* Makes an authentication id from a request id and a dataset id.
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index ace8451..2b39bb4 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -165,9 +165,11 @@
import java.util.stream.Collectors;
/**
- * The Chooser Activity handles intent resolution specifically for sharing intents -
- * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
+ * This is the legacy ChooserActivity and is not expected to be invoked, it's only here because
+ * MediaAppSelectorActivity is still depending on it. The actual chooser used by the system is
+ * at packages/modules/IntentResolver/java/src/com/android/intentresolver/ChooserActivity.java
*
+ * The migration to the new package will be completed in a later release.
*/
public class ChooserActivity extends ResolverActivity implements
ChooserListAdapter.ChooserListCommunicator,
diff --git a/core/proto/android/companion/telecom.proto b/core/proto/android/companion/telecom.proto
index b90067d..700baa1 100644
--- a/core/proto/android/companion/telecom.proto
+++ b/core/proto/android/companion/telecom.proto
@@ -42,6 +42,9 @@
ONGOING = 2;
ON_HOLD = 3;
RINGING_SILENCED = 4;
+ AUDIO_PROCESSING = 5;
+ RINGING_SIMULATED = 6;
+ DISCONNECTED = 7;
}
Status status = 3;
@@ -89,8 +92,6 @@
END = 6;
PUT_ON_HOLD = 7;
TAKE_OFF_HOLD = 8;
- REJECT_AND_BLOCK = 9;
- IGNORE = 10;
}
// The list of active calls.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 63afc34..2f9f6ae 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7692,22 +7692,6 @@
android:defaultToDeviceProtectedStorage="true"
android:forceQueryable="true"
android:directBootAware="true">
- <activity android:name="com.android.internal.app.ChooserActivity"
- android:theme="@style/Theme.DeviceDefault.Chooser"
- android:finishOnCloseSystemDialogs="true"
- android:excludeFromRecents="true"
- android:documentLaunchMode="never"
- android:relinquishTaskIdentity="true"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
- android:process=":ui"
- android:exported="true"
- android:visibleToInstantApps="true">
- <intent-filter android:priority="100">
- <action android:name="android.intent.action.CHOOSER" />
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.VOICE" />
- </intent-filter>
- </activity>
<activity android:name="com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity"
android:exported="false"
android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 56616cb..ed3aadd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -51,6 +51,8 @@
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
+import java.util.function.Consumer;
+
/**
* Handles split decor like showing resizing hint for a specific split.
*/
@@ -251,7 +253,7 @@
}
/** Stops showing resizing hint. */
- public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+ public void onResized(SurfaceControl.Transaction t, Consumer<Boolean> animFinishedCallback) {
if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
mScreenshotAnimator.cancel();
}
@@ -281,7 +283,7 @@
mScreenshot = null;
if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
- animFinishedCallback.run();
+ animFinishedCallback.accept(true);
}
}
});
@@ -313,12 +315,12 @@
}
}
if (mShown) {
- fadeOutDecor(animFinishedCallback);
+ fadeOutDecor(()-> animFinishedCallback.accept(true));
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
- animFinishedCallback.run();
+ animFinishedCallback.accept(false);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 2832c55..9a2ec15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -717,6 +717,10 @@
return bounds.width() > bounds.height();
}
+ public boolean isDensityChanged(int densityDpi) {
+ return mDensity != densityDpi;
+ }
+
/**
* Return if this layout is landscape.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 042721c9..0289da9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -78,8 +78,15 @@
return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null;
}
- /** Returns true if they are the same package. */
- public static boolean samePackage(String packageName1, String packageName2) {
- return packageName1 != null && packageName1.equals(packageName2);
+ /** Retrieve user id from a taskId */
+ public static int getUserId(int taskId, ShellTaskOrganizer taskOrganizer) {
+ final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
+ return taskInfo != null ? taskInfo.userId : -1;
+ }
+
+ /** Returns true if package names and user ids match. */
+ public static boolean samePackage(String packageName1, String packageName2,
+ int userId1, int userId2) {
+ return (packageName1 != null && packageName1.equals(packageName2)) && (userId1 == userId2);
}
}
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 be0288e..4980e49 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
@@ -201,6 +201,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
+ Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController) {
@@ -212,6 +213,7 @@
taskOrganizer,
displayController,
syncQueue,
+ transitions,
desktopModeController,
desktopTasksController,
splitScreenController);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index df94b41..fb08c87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -266,6 +266,7 @@
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) : new Bundle();
+ final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
if (isTask) {
final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
@@ -273,14 +274,14 @@
} else if (isShortcut) {
final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
- final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
// Put BAL flags to avoid activity start aborted.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
- mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts);
+ mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
+ position, opts);
}
}
@@ -334,8 +335,8 @@
void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options);
void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
@Nullable Bundle options, UserHandle user);
- void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options);
+ void startIntent(PendingIntent intent, int userId, Intent fillInIntent,
+ @SplitPosition int position, @Nullable Bundle options);
void enterSplitScreen(int taskId, boolean leftOrTop);
/**
@@ -379,8 +380,8 @@
}
@Override
- public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, int position,
- @Nullable Bundle options) {
+ public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
+ int position, @Nullable Bundle options) {
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index f8e1435..9e6bd47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -946,10 +946,15 @@
mPipBoundsState.getDisplayBounds().right,
mPipBoundsState.getDisplayBounds().bottom);
mPipBoundsState.addNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG, rect);
+ updatePipPositionForKeepClearAreas();
} else {
mPipBoundsState.removeNamedUnrestrictedKeepClearArea(LAUNCHER_KEEP_CLEAR_AREA_TAG);
+ // postpone moving in response to hide of Launcher in case there's another change
+ mMainExecutor.removeCallbacks(mMovePipInResponseToKeepClearAreasChangeCallback);
+ mMainExecutor.executeDelayed(
+ mMovePipInResponseToKeepClearAreasChangeCallback,
+ PIP_KEEP_CLEAR_AREAS_DELAY);
}
- updatePipPositionForKeepClearAreas();
}
private void setLauncherAppIconSize(int iconSizePx) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index f819bee..c414e70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -73,8 +73,8 @@
/**
* Starts an activity in a stage.
*/
- oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int position,
- in Bundle options, in InstanceId instanceId) = 9;
+ oneway void startIntent(in PendingIntent intent, int userId, in Intent fillInIntent,
+ int position, in Bundle options, in InstanceId instanceId) = 9;
/**
* Starts tasks simultaneously in one transition.
@@ -86,8 +86,8 @@
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
- in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, int userId1, in Bundle options1,
+ int taskId, in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -107,7 +107,7 @@
/**
* Starts a pair of intent and task using legacy transition system.
*/
- oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
+ oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent, int userId1,
in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
@@ -121,18 +121,18 @@
/**
* Start a pair of intents using legacy transition system.
*/
- oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+ oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1, int userId1,
in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
- in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition, float splitRatio,
- in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
+ int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 18;
/**
* Start a pair of intents in one transition.
*/
- oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1,
- in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2,
- in Bundle options2, int splitPosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+ oneway void startIntents(in PendingIntent pendingIntent1, int userId1,
+ in ShortcutInfo shortcutInfo1, in Bundle options1, in PendingIntent pendingIntent2,
+ int userId2, in ShortcutInfo shortcutInfo2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
/**
* Blocking call that notifies and gets additional split-screen targets when entering
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c654c1b..34701f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -502,7 +502,8 @@
if (options == null) options = new Bundle();
final ActivityOptions activityOptions = ActivityOptions.fromBundle(options);
- if (samePackage(packageName, getPackageName(reverseSplitPosition(position)))) {
+ if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
+ user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
if (supportMultiInstancesSplit(packageName)) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -531,7 +532,9 @@
final String packageName1 = shortcutInfo.getPackage();
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2)) {
+ final int userId1 = shortcutInfo.getUserId();
+ final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -558,7 +561,9 @@
// NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
// recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2)) {
+ final int userId1 = shortcutInfo.getUserId();
+ final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
activityOptions.setApplyMultipleTaskFlagForShortcut(true);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -578,23 +583,24 @@
}
/**
- * See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
+ * See {@link #startIntent(PendingIntent, int, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
*/
- public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
+ public void startIntent(PendingIntent intent, int userId, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
mStageCoordinator.onRequestToSplit(instanceId, ENTER_REASON_LAUNCHER);
- startIntent(intent, fillInIntent, position, options);
+ startIntent(intent, userId, fillInIntent, position, options);
}
- private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2)) {
+ final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -611,15 +617,17 @@
options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
}
- private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
- int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ private void startIntentAndTask(PendingIntent pendingIntent, int userId1,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
// NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
// recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
- if (samePackage(packageName1, packageName2)) {
+ final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -639,16 +647,16 @@
options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
- private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
- if (samePackage(packageName1, packageName2)) {
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -668,16 +676,16 @@
splitPosition, splitRatio, adapter, instanceId);
}
- private void startIntents(PendingIntent pendingIntent1,
+ private void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent1 = null;
Intent fillInIntent2 = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
- if (samePackage(packageName1, packageName2)) {
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
fillInIntent1 = new Intent();
fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -698,7 +706,7 @@
}
@Override
- public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
+ public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
// Flag this as a no-user-action launch to prevent sending user leaving event to the current
// top activity since it's going to be put into another side of the split. This prevents the
@@ -708,7 +716,8 @@
final String packageName1 = SplitScreenUtils.getPackageName(intent);
final String packageName2 = getPackageName(reverseSplitPosition(position));
- if (SplitScreenUtils.samePackage(packageName1, packageName2)) {
+ final int userId2 = getUserId(reverseSplitPosition(position));
+ if (samePackage(packageName1, packageName2, userId1, userId2)) {
if (supportMultiInstancesSplit(packageName1)) {
// To prevent accumulating large number of instances in the background, reuse task
// in the background with priority.
@@ -761,6 +770,24 @@
return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
}
+ /** Retrieve user id of a specific split position if split screen is activated, otherwise
+ * returns the user id of the top running task. */
+ private int getUserId(@SplitPosition int position) {
+ ActivityManager.RunningTaskInfo taskInfo;
+ if (isSplitScreenVisible()) {
+ taskInfo = getTaskInfo(position);
+ } else {
+ taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask())
+ .orElse(null);
+ if (!isValidToSplit(taskInfo)) {
+ return -1;
+ }
+ }
+
+ return taskInfo != null ? taskInfo.userId : -1;
+ }
+
@VisibleForTesting
boolean supportMultiInstancesSplit(String packageName) {
if (packageName != null) {
@@ -1069,14 +1096,14 @@
}
@Override
- public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
- options1, taskId, options2, splitPosition, splitRatio, adapter,
- instanceId));
+ userId1, options1, taskId, options2, splitPosition, splitRatio,
+ adapter, instanceId));
}
@Override
@@ -1102,13 +1129,14 @@
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
- int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, @Nullable RemoteTransition remoteTransition,
- InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, int userId1,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
- options2, splitPosition, splitRatio, remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, userId1, options1,
+ taskId, options2, splitPosition, splitRatio, remoteTransition,
+ instanceId));
}
@Override
@@ -1122,29 +1150,29 @@
}
@Override
- public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
- PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2,
+ PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
(controller) ->
- controller.startIntentsWithLegacyTransition(pendingIntent1, shortcutInfo1,
- options1, pendingIntent2, shortcutInfo2, options2, splitPosition,
- splitRatio, adapter, instanceId)
+ controller.startIntentsWithLegacyTransition(pendingIntent1, userId1,
+ shortcutInfo1, options1, pendingIntent2, userId2, shortcutInfo2,
+ options2, splitPosition, splitRatio, adapter, instanceId)
);
}
@Override
- public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1,
- @Nullable Bundle options1, PendingIntent pendingIntent2,
- @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
+ public void startIntents(PendingIntent pendingIntent1, int userId1,
+ @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntents",
(controller) ->
- controller.startIntents(pendingIntent1, shortcutInfo1,
- options1, pendingIntent2, shortcutInfo2, options2,
+ controller.startIntents(pendingIntent1, userId1, shortcutInfo1,
+ options1, pendingIntent2, userId2, shortcutInfo2, options2,
splitPosition, splitRatio, remoteTransition, instanceId)
);
}
@@ -1158,11 +1186,11 @@
}
@Override
- public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
+ public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> controller.startIntent(intent, fillInIntent, position, options,
- instanceId));
+ (controller) -> controller.startIntent(intent, userId, fillInIntent, position,
+ options, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index ee36d52..c15ab0a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -208,11 +208,13 @@
mAnimations.add(va);
decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
- decor.onResized(startTransaction, () -> {
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish(null /* wct */, null /* wctCB */);
- });
+ decor.onResized(startTransaction, animated -> {
+ mAnimations.remove(va);
+ if (animated) {
+ mTransitions.getMainExecutor().execute(() -> {
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ }
});
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index fcea9ad..ebf464c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2175,6 +2175,14 @@
return;
}
mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
+
+ if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi)
+ && mMainStage.isActive()
+ && mSplitLayout.updateConfiguration(newConfig)
+ && ENABLE_SHELL_TRANSITIONS) {
+ mSplitLayout.update(null /* t */);
+ onLayoutSizeChanged(mSplitLayout);
+ }
}
void updateSurfaces(SurfaceControl.Transaction transaction) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index fe2faaf..2e7fca3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -135,6 +135,21 @@
}
/**
+ * Looks through the pending transitions for a opening transaction that matches the provided
+ * `taskView`.
+ * @param taskView the pending transition should be for this.
+ */
+ private PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) {
+ for (int i = mPending.size() - 1; i >= 0; --i) {
+ if (mPending.get(i).mTaskView != taskView) continue;
+ if (TransitionUtil.isOpeningType(mPending.get(i).mType)) {
+ return mPending.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
* @param type the type of transition it's looking for
@@ -149,6 +164,19 @@
return null;
}
+ /**
+ * Returns all the pending transitions for a given `taskView`.
+ * @param taskView the pending transition should be for this.
+ */
+ ArrayList<PendingTransition> findAllPending(TaskViewTaskController taskView) {
+ ArrayList<PendingTransition> list = new ArrayList<>();
+ for (int i = mPending.size() - 1; i >= 0; --i) {
+ if (mPending.get(i).mTaskView != taskView) continue;
+ list.add(mPending.get(i));
+ }
+ return list;
+ }
+
private PendingTransition findPending(IBinder claimed) {
for (int i = 0; i < mPending.size(); ++i) {
if (mPending.get(i).mClaimed != claimed) continue;
@@ -249,9 +277,10 @@
// Task view isn't visible, the bounds will next visibility update.
return;
}
- if (hasPending()) {
- // There is already a transition in-flight, the window bounds will be set in
- // prepareOpenAnimation.
+ PendingTransition pendingOpen = findPendingOpeningTransition(taskView);
+ if (pendingOpen != null) {
+ // There is already an opening transition in-flight, the window bounds will be
+ // set in prepareOpenAnimation (via the window crop) if needed.
return;
}
WindowContainerTransaction wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index ef0c7a8..6d37e58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -235,11 +235,20 @@
public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
TransitionInfo info, SurfaceControl.Transaction t,
@Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
+ return newTarget(change, order, false /* forceTranslucent */, info, t, leashMap);
+ }
+
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change info
+ */
+ public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+ boolean forceTranslucent, TransitionInfo info, SurfaceControl.Transaction t,
+ @Nullable ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
final SurfaceControl leash = createLeash(info, change, order, t);
if (leashMap != null) {
leashMap.put(change.getLeash(), leash);
}
- return newTarget(change, order, leash);
+ return newTarget(change, order, leash, forceTranslucent);
}
/**
@@ -247,6 +256,14 @@
*/
public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
SurfaceControl leash) {
+ return newTarget(change, order, leash, false /* forceTranslucent */);
+ }
+
+ /**
+ * Creates a new RemoteAnimationTarget from the provided change and leash
+ */
+ public static RemoteAnimationTarget newTarget(TransitionInfo.Change change, int order,
+ SurfaceControl leash, boolean forceTranslucent) {
if (isDividerBar(change)) {
return getDividerTarget(change, leash);
}
@@ -276,7 +293,7 @@
// TODO: once we can properly sync transactions across process,
// then get rid of this leash.
leash,
- (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
+ forceTranslucent || (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0,
null,
// TODO(shell-transitions): we need to send content insets? evaluate how its used.
new Rect(0, 0, 0, 0),
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 54babce..9fd57d7 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
@@ -104,6 +104,7 @@
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
+ private final Transitions mTransitions;
private Optional<SplitScreenController> mSplitScreenController;
@@ -118,6 +119,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
+ Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController) {
@@ -128,6 +130,7 @@
taskOrganizer,
displayController,
syncQueue,
+ transitions,
desktopModeController,
desktopTasksController,
splitScreenController,
@@ -144,6 +147,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
+ Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController,
@@ -158,6 +162,7 @@
mDisplayController = displayController;
mSplitScreenController = splitScreenController;
mSyncQueue = syncQueue;
+ mTransitions = transitions;
mDesktopModeController = desktopModeController;
mDesktopTasksController = desktopTasksController;
@@ -818,7 +823,8 @@
} else {
windowDecoration.createResizeVeil();
return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, disallowedAreaForEndBounds, mDragStartListener);
+ mDisplayController, disallowedAreaForEndBounds, mDragStartListener,
+ mTransitions);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index b785ef0..8277109 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -44,7 +44,7 @@
* Creates and updates a veil that covers task contents on resize.
*/
public class ResizeVeil {
- private static final int RESIZE_ALPHA_DURATION = 200;
+ private static final int RESIZE_ALPHA_DURATION = 100;
private final Context mContext;
private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
@@ -155,7 +155,6 @@
* Animate veil's alpha to 0, fading it out.
*/
public void hideVeil() {
- final View resizeVeilView = mViewHost.getView();
final ValueAnimator animator = new ValueAnimator();
animator.setFloatValues(1, 0);
animator.setDuration(RESIZE_ALPHA_DURATION);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 56475a8..58c78e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -16,13 +16,22 @@
package com.android.wm.shell.windowdecor;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.IBinder;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
@@ -32,12 +41,14 @@
* If the drag is resizing the task, we resize the veil instead.
* If the drag is repositioning, we update in the typical manner.
*/
-public class VeiledResizeTaskPositioner implements DragPositioningCallback {
+public class VeiledResizeTaskPositioner implements DragPositioningCallback,
+ Transitions.TransitionHandler {
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
private DisplayController mDisplayController;
private DragPositioningCallbackUtility.DragStartListener mDragStartListener;
+ private final Transitions mTransitions;
private final Rect mStableBounds = new Rect();
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
@@ -46,28 +57,29 @@
// finalize the bounds there using WCT#setBounds
private final Rect mDisallowedAreaForEndBounds = new Rect();
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
- private boolean mHasDragResized;
private int mCtrlType;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
Rect disallowedAreaForEndBounds,
- DragPositioningCallbackUtility.DragStartListener dragStartListener) {
+ DragPositioningCallbackUtility.DragStartListener dragStartListener,
+ Transitions transitions) {
this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
- dragStartListener, SurfaceControl.Transaction::new);
+ dragStartListener, SurfaceControl.Transaction::new, transitions);
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
Rect disallowedAreaForEndBounds,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
- Supplier<SurfaceControl.Transaction> supplier) {
+ Supplier<SurfaceControl.Transaction> supplier, Transitions transitions) {
mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
mTransactionSupplier = supplier;
+ mTransitions = transitions;
}
@Override
@@ -84,7 +96,6 @@
mTaskOrganizer.applyTransaction(wct);
}
}
- mHasDragResized = false;
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
}
@@ -96,7 +107,6 @@
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
- mHasDragResized = true;
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -111,18 +121,23 @@
PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y,
mRepositionStartPoint);
if (isResizing()) {
- if (mHasDragResized) {
+ if (!mTaskBoundsAtDragStart.equals(mRepositionTaskBounds)) {
DragPositioningCallbackUtility.changeBounds(
mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds,
delta, mDisplayController, mDesktopWindowDecoration);
- DragPositioningCallbackUtility.applyTaskBoundsChange(
- new WindowContainerTransaction(), mDesktopWindowDecoration,
- mRepositionTaskBounds, mTaskOrganizer);
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
+ } else {
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ } else {
+ // If bounds haven't changed, perform necessary veil reset here as startAnimation
+ // won't be called.
+ mDesktopWindowDecoration.hideResizeVeil();
}
- // TODO: (b/279062291) Synchronize the start of hide to the end of the draw triggered
- // above.
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
- mDesktopWindowDecoration.hideResizeVeil();
} else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) {
DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y);
@@ -133,7 +148,6 @@
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
- mHasDragResized = false;
}
private boolean isResizing() {
@@ -141,4 +155,26 @@
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ mDesktopWindowDecoration.hideResizeVeil();
+ mCtrlType = CTRL_TYPE_UNDEFINED;
+ finishCallback.onTransitionFinished(null, null);
+ return true;
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 9e988e8..7c1da35 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -211,7 +211,7 @@
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_UNDEFINED), any());
}
@@ -223,12 +223,12 @@
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -240,12 +240,12 @@
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
- verify(mSplitScreenStarter).startIntent(any(), any(),
+ verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d0e2601..c37a497 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -181,7 +181,8 @@
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
@@ -200,7 +201,8 @@
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
- mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
@@ -223,7 +225,8 @@
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
- mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
@@ -246,7 +249,8 @@
doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
.findTaskInBackground(any());
- mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
isNull());
@@ -265,7 +269,8 @@
doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
SPLIT_POSITION_BOTTOM_OR_RIGHT);
- mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+ mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
+ SPLIT_POSITION_TOP_OR_LEFT, null);
verify(mStageCoordinator).switchSplitPosition(anyString());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
index 9d56686..71ad0d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTransitionsTest.java
@@ -45,6 +45,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -58,6 +60,12 @@
ActivityManager.RunningTaskInfo mTaskInfo;
@Mock
WindowContainerToken mToken;
+ @Mock
+ TaskViewTaskController mTaskViewTaskController2;
+ @Mock
+ ActivityManager.RunningTaskInfo mTaskInfo2;
+ @Mock
+ WindowContainerToken mToken2;
TaskViewTransitions mTaskViewTransitions;
@@ -73,10 +81,16 @@
mTaskInfo.token = mToken;
mTaskInfo.taskId = 314;
mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class);
+ when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
+
+ mTaskInfo2 = new ActivityManager.RunningTaskInfo();
+ mTaskInfo2.token = mToken2;
+ mTaskInfo2.taskId = 315;
+ mTaskInfo2.taskDescription = mock(ActivityManager.TaskDescription.class);
+ when(mTaskViewTaskController2.getTaskInfo()).thenReturn(mTaskInfo2);
mTaskViewTransitions = spy(new TaskViewTransitions(mTransitions));
mTaskViewTransitions.addTaskView(mTaskViewTaskController);
- when(mTaskViewTaskController.getTaskInfo()).thenReturn(mTaskInfo);
}
@Test
@@ -119,7 +133,7 @@
}
@Test
- public void testSetTaskBounds_taskVisibleWithPending_noTransaction() {
+ public void testSetTaskBounds_taskVisibleWithPendingOpen_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
@@ -135,6 +149,43 @@
}
@Test
+ public void testSetTaskBounds_taskVisibleWithPendingChange_transition() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
+
+ // Consume the pending transition from visibility change
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending).isNotNull();
+ mTaskViewTransitions.startAnimation(pending.mClaimed,
+ mock(TransitionInfo.class),
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+ // Verify it was consumed
+ TaskViewTransitions.PendingTransition checkPending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(checkPending).isNull();
+
+ // Test that set bounds creates a new transition
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
+ .isNotNull();
+
+ // Test that set bounds again (with different bounds) creates another transition
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 300, 200));
+ List<TaskViewTransitions.PendingTransition> pendingList =
+ mTaskViewTransitions.findAllPending(mTaskViewTaskController)
+ .stream()
+ .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
+ .toList();
+ assertThat(pendingList.size()).isEqualTo(2);
+ }
+
+ @Test
public void testSetTaskBounds_sameBounds_noTransaction() {
assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
@@ -161,6 +212,16 @@
mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE);
assertThat(pendingBounds).isNotNull();
+ // Test that setting same bounds with in-flight transition doesn't cause another one
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ List<TaskViewTransitions.PendingTransition> pendingList =
+ mTaskViewTransitions.findAllPending(mTaskViewTaskController)
+ .stream()
+ .filter(pendingTransition -> pendingTransition.mType == TRANSIT_CHANGE)
+ .toList();
+ assertThat(pendingList.size()).isEqualTo(1);
+
// Consume the pending bounds transaction
mTaskViewTransitions.startAnimation(pendingBounds.mClaimed,
mock(TransitionInfo.class),
@@ -180,6 +241,42 @@
assertThat(pendingBounds2).isNull();
}
+
+ @Test
+ public void testSetTaskBounds_taskVisibleWithDifferentTaskViewPendingChange_transition() {
+ assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+
+ mTaskViewTransitions.addTaskView(mTaskViewTaskController2);
+
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController, true);
+
+ // Consume the pending transition from visibility change
+ TaskViewTransitions.PendingTransition pending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(pending).isNotNull();
+ mTaskViewTransitions.startAnimation(pending.mClaimed,
+ mock(TransitionInfo.class),
+ new SurfaceControl.Transaction(),
+ new SurfaceControl.Transaction(),
+ mock(Transitions.TransitionFinishCallback.class));
+ // Verify it was consumed
+ TaskViewTransitions.PendingTransition checkPending =
+ mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_TO_FRONT);
+ assertThat(checkPending).isNull();
+
+ // Set the second taskview as visible & check that it has a pending transition
+ mTaskViewTransitions.setTaskViewVisible(mTaskViewTaskController2, true);
+ TaskViewTransitions.PendingTransition pending2 =
+ mTaskViewTransitions.findPending(mTaskViewTaskController2, TRANSIT_TO_FRONT);
+ assertThat(pending2).isNotNull();
+
+ // Test that set bounds on the first taskview will create a new transition
+ mTaskViewTransitions.setTaskBounds(mTaskViewTaskController,
+ new Rect(0, 0, 100, 100));
+ assertThat(mTaskViewTransitions.findPending(mTaskViewTaskController, TRANSIT_CHANGE))
+ .isNotNull();
+ }
+
@Test
public void testSetTaskVisibility_taskRemoved_noNPE() {
mTaskViewTransitions.removeTaskView(mTaskViewTaskController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
index 4c27706..41bab95 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java
@@ -54,6 +54,7 @@
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -87,6 +88,7 @@
@Mock private DesktopTasksController mDesktopTasksController;
@Mock private InputMonitor mInputMonitor;
@Mock private InputManager mInputManager;
+ @Mock private Transitions mTransitions;
@Mock private DesktopModeWindowDecorViewModel.InputMonitorFactory mMockInputMonitorFactory;
@Mock private Supplier<SurfaceControl.Transaction> mTransactionFactory;
@Mock private SurfaceControl.Transaction mTransaction;
@@ -106,6 +108,7 @@
mTaskOrganizer,
mDisplayController,
mSyncQueue,
+ mTransitions,
Optional.of(mDesktopModeController),
Optional.of(mDesktopTasksController),
Optional.of(mSplitScreenController),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 337e40d..4147dd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -22,12 +22,14 @@
import android.testing.AndroidTestingRunner
import android.view.Display
import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -78,6 +80,8 @@
private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
@Mock
private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock
+ private lateinit var mockTransitions: Transitions
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -92,7 +96,8 @@
mockDisplayController,
DISALLOWED_AREA_FOR_END_BOUNDS,
mockDragStartListener,
- mockTransactionFactory
+ mockTransactionFactory,
+ mockTransitions
)
whenever(taskToken.asBinder()).thenReturn(taskBinder)
@@ -129,6 +134,12 @@
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
+ verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
+ eq(taskPositioner))
verify(mockDesktopWindowDecoration).hideResizeVeil()
}
@@ -206,15 +217,12 @@
rectAfterEnd.right += 10
rectAfterEnd.top += 10
verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
- verify(mockDesktopWindowDecoration).hideResizeVeil()
-
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- })
+ change.configuration.windowConfiguration.bounds == rectAfterEnd}},
+ eq(taskPositioner))
}
@Test
@@ -235,7 +243,13 @@
STARTING_BOUNDS.left.toFloat() + 10,
STARTING_BOUNDS.top.toFloat() + 10
)
- verify(mockDesktopWindowDecoration).hideResizeVeil()
+
+ verify(mockTransitions, never()).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
+ eq(taskPositioner))
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 7e238e4..0e9c162 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -57,6 +57,30 @@
}
};
+ /**
+ * The {@link #getOriginalId() original id} of the route that represents the built-in media
+ * route.
+ *
+ * <p>A route with this id will only be visible to apps with permission to do system routing,
+ * which means having {@link android.Manifest.permission#BLUETOOTH_CONNECT} and {@link
+ * android.Manifest.permission#BLUETOOTH_SCAN}, or {@link
+ * android.Manifest.permission#MODIFY_AUDIO_ROUTING}.
+ *
+ * @hide
+ */
+ public static final String ROUTE_ID_DEVICE = "DEVICE_ROUTE";
+
+ /**
+ * The {@link #getOriginalId() original id} of the route that represents the default system
+ * media route.
+ *
+ * <p>A route with this id will be visible to apps with no permission over system routing. See
+ * {@link #ROUTE_ID_DEVICE} for details.
+ *
+ * @hide
+ */
+ public static final String ROUTE_ID_DEFAULT = "DEFAULT_ROUTE";
+
/** @hide */
@IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTED})
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-night-v34/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-night-v34/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..a7ccfc2
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-night-v34/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@android:color/system_accent1_100"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@android:color/system_accent1_100"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v31/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v31/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..de367244
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v31/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@android:color/black"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@android:color/black"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/color-v34/settingslib_main_switch_text_color.xml b/packages/SettingsLib/MainSwitchPreference/res/color-v34/settingslib_main_switch_text_color.xml
new file mode 100644
index 0000000..e33905b
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/color-v34/settingslib_main_switch_text_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:color="@android:color/system_accent1_900"
+ android:alpha="?android:attr/disabledAlpha" />
+ <item android:color="@android:color/system_accent1_900"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
index 1b80cc6..b8e43fc2 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
@@ -20,6 +20,6 @@
<style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title.Inverse">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
- <item name="android:textColor">@android:color/black</item>
+ <item name="android:textColor">@color/settingslib_main_switch_text_color</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-night-v34/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-night-v34/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..6fab831
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-night-v34/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@android:color/system_neutral2_400"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@android:color/system_neutral2_400" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_thumb_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_thumb_color.xml
index 8ccbb06..a9c5c03 100644
--- a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_thumb_color.xml
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_switch_thumb_color.xml
@@ -17,7 +17,8 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Disabled status of thumb -->
<item android:state_enabled="false"
- android:color="@color/settingslib_thumb_disabled_color" />
+ android:color="@color/settingslib_thumb_disabled_color"
+ android:alpha="?android:attr/disabledAlpha" />
<!-- Toggle off status of thumb -->
<item android:state_checked="false"
android:color="@color/settingslib_thumb_off_color" />
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_thumb_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_thumb_color.xml
new file mode 100644
index 0000000..2bc4ddf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_thumb_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@color/settingslib_thumb_disabled_color"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@color/settingslib_thumb_off_color" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_thumb_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_track_outline_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_track_outline_color.xml
new file mode 100644
index 0000000..8abc1fd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v34/settingslib_switch_track_outline_color.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Disabled status of thumb -->
+ <item android:state_enabled="false"
+ android:color="@android:color/system_neutral2_500"
+ android:alpha="?android:attr/disabledAlpha" />
+ <!-- Toggle off status of thumb -->
+ <item android:state_checked="false"
+ android:color="@android:color/system_neutral2_500" />
+ <!-- Enabled or toggle on status of thumb -->
+ <item android:color="@color/settingslib_track_on_color" />
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v34/settingslib_switch_track.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v34/settingslib_switch_track.xml
new file mode 100644
index 0000000..851e413
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v34/settingslib_switch_track.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="52dp"
+ android:height="28dp">
+
+ <solid android:color="@color/settingslib_switch_track_color" />
+ <corners android:radius="35dp" />
+ <stroke android:width="2dp" android:color="@color/settingslib_track_online_color"/>
+</shape>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
new file mode 100644
index 0000000..e3645b5
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@android:color/system_accent1_700</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@android:color/system_accent1_700</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_thumb_off_color</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@android:color/system_accent1_800</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@android:color/system_neutral2_400</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@android:color/system_accent1_200</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@android:color/system_surface_container_highest_dark
+ </color>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
new file mode 100644
index 0000000..fdd96ec
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<resources>
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@android:color/system_accent1_100</color>
+
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@android:color/system_accent1_100</color>
+
+ <!-- Material next thumb disable color-->
+ <color name="settingslib_thumb_disabled_color">@color/settingslib_thumb_off_color</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_on_color">@android:color/system_accent1_0</color>
+
+ <!-- Material next thumb off color-->
+ <color name="settingslib_thumb_off_color">@android:color/system_neutral2_500</color>
+
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@android:color/system_accent1_600</color>
+
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@android:color/system_surface_container_highest_light</color>
+
+ <!-- Material next track outline color-->
+ <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml
new file mode 100644
index 0000000..f7dac13
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/bouncer_message_view.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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
+ -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <com.android.keyguard.BouncerKeyguardMessageArea
+ android:id="@+id/bouncer_primary_message_area"
+ style="@style/Keyguard.Bouncer.PrimaryMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/keyguard_lock_padding"
+ android:focusable="true"
+ />
+
+ <com.android.keyguard.BouncerKeyguardMessageArea
+ android:id="@+id/bouncer_secondary_message_area"
+ style="@style/Keyguard.Bouncer.SecondaryMessage"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/secondary_message_padding"
+ android:focusable="true" />
+
+</merge>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 4b94707..3fc0965 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -25,7 +25,7 @@
android:layout_height="wrap_content"
android:clipChildren="false"
android:layout_gravity="center_horizontal|top">
- <FrameLayout
+ <com.android.keyguard.KeyguardClockFrame
android:id="@+id/lockscreen_clock_view"
android:layout_width="wrap_content"
android:layout_height="@dimen/small_clock_height"
@@ -34,7 +34,7 @@
android:clipChildren="false"
android:paddingStart="@dimen/clock_padding_start"
android:visibility="invisible" />
- <FrameLayout
+ <com.android.keyguard.KeyguardClockFrame
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 6ec65ce..3412a30 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -29,6 +29,13 @@
>
<include layout="@layout/keyguard_bouncer_message_area"/>
+ <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ />
+
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
index c772c96..78a7c17 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_view.xml
@@ -33,6 +33,12 @@
android:clipToPadding="false">
<include layout="@layout/keyguard_bouncer_message_area"/>
+ <com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pattern_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index f61df05..330676b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -29,6 +29,12 @@
androidprv:layout_maxWidth="@dimen/keyguard_security_width">
<include layout="@layout/keyguard_bouncer_message_area"/>
+<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pin_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index 647abee..557fbf2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -30,7 +30,7 @@
android:clipChildren="false"
android:layout_width="0dp"
android:layout_height="wrap_content">
- <LinearLayout
+ <com.android.keyguard.KeyguardStatusContainer
android:id="@+id/status_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -48,5 +48,5 @@
android:layout_height="wrap_content"
android:padding="@dimen/qs_media_padding"
/>
- </LinearLayout>
+ </com.android.keyguard.KeyguardStatusContainer>
</com.android.keyguard.KeyguardStatusView>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 4d289eb..88f7bcd 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -23,6 +23,26 @@
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ </style>
+ <style name="Keyguard.Bouncer.PrimaryMessage" parent="Theme.SystemUI">
+ <item name="android:textSize">18sp</item>
+ <item name="android:lineHeight">24dp</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:textAlignment">center</item>
+ <item name="android:ellipsize">marquee</item>
+ </style>
+ <style name="Keyguard.Bouncer.SecondaryMessage" parent="Theme.SystemUI">
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20dp</item>
+ <item name="android:maxLines">@integer/bouncer_secondary_message_lines</item>
+ <item name="android:lines">@integer/bouncer_secondary_message_lines</item>
+ <item name="android:textAlignment">center</item>
+ <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item>
diff --git a/packages/SystemUI/res/raw/sfps_pulse.json b/packages/SystemUI/res/raw/sfps_pulse.json
index c4903a2..9fe4eb6 100644
--- a/packages/SystemUI/res/raw/sfps_pulse.json
+++ b/packages/SystemUI/res/raw/sfps_pulse.json
@@ -1 +1 @@
-{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"Fingerprint Pulse Motion","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[28,40,0],"to":[0.751,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
+{"v":"5.7.13","fr":60,"ip":0,"op":300,"w":42,"h":80,"nm":"sfps_pulse_motion_04","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.617,"y":0.539},"o":{"x":0.477,"y":0},"t":0,"s":[28,40,0],"to":[0.365,0,0],"ti":[-1.009,0,0]},{"i":{"x":0.436,"y":1},"o":{"x":0.17,"y":0.547},"t":10,"s":[30.576,40,0],"to":[1.064,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[32.503,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[28,40,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[32.503,40,0],"to":[0,0,0],"ti":[0.751,0,0]},{"t":300,"s":[28,40,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[41.878,40,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[75]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[75]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[75]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[75]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[75]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[75]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[75]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[75]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[75]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[75]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[75]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.587,40,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.858823537827,0.803921580315,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/raw/sfps_pulse_landscape.json b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
index 8c91762..42b92eb 100644
--- a/packages/SystemUI/res/raw/sfps_pulse_landscape.json
+++ b/packages/SystemUI/res/raw/sfps_pulse_landscape.json
@@ -1 +1 @@
-{"v":"5.7.6","fr":60,"ip":0,"op":300,"w":80,"h":42,"nm":"Fingerprint Pulse Motion Portrait","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[40,14,0],"to":[0,-0.751,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,-0.751,0]},{"t":300,"s":[40,14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,0.122,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[20]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[20]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[20]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[20]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[20]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[20]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[20]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[20]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[20]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[20]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[20]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
+{"v":"5.7.13","fr":60,"ip":0,"op":300,"w":80,"h":42,"nm":"sfps_pulse_motion_portrait","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".black","cl":"black","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":0,"s":[40,14,0],"to":[0,-0.751,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":60,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":90,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":120,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":150,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":180,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":210,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.2,"y":1},"o":{"x":0.3,"y":0},"t":240,"s":[40,14,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":270,"s":[40,9.497,0],"to":[0,0,0],"ti":[0,-0.751,0]},{"t":300,"s":[40,14,0]}],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[1.566,0],[-3.929,-5.503],[-2.751,-6.68],[3.929,0],[-2.751,6.68],[-3.929,5.503]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.218,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".blue600","cl":"blue600","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,0.122,0],"ix":2,"l":2},"a":{"a":0,"k":[28.253,40,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[55,55],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 2","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.101960785687,0.450980395079,0.909803926945,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[28.253,40],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[75]},{"t":30,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":-30,"s":[55,55]},{"t":30,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":30,"st":-30,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[75]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":0,"s":[55,55]},{"t":60,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":70,"s":[75]},{"t":90,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":30,"s":[55,55]},{"t":90,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":90,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":100,"s":[75]},{"t":120,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":60,"s":[55,55]},{"t":120,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":120,"st":60,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":130,"s":[75]},{"t":150,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":90,"s":[55,55]},{"t":150,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":90,"op":150,"st":90,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":160,"s":[75]},{"t":180,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":120,"s":[55,55]},{"t":180,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":120,"op":180,"st":120,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":190,"s":[75]},{"t":210,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":150,"s":[55,55]},{"t":210,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":150,"op":210,"st":150,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":220,"s":[75]},{"t":240,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":180,"s":[55,55]},{"t":240,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":180,"op":240,"st":180,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":250,"s":[75]},{"t":270,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":210,"s":[55,55]},{"t":270,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":210,"op":270,"st":210,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":280,"s":[75]},{"t":300,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":240,"s":[55,55]},{"t":300,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":240,"op":300,"st":240,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":310,"s":[75]},{"t":330,"s":[0]}],"ix":11},"r":{"a":0,"k":-90,"ix":10},"p":{"a":0,"k":[40,16.413,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.25,0.25],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":270,"s":[55,55]},{"t":330,"s":[80,80]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.40000000596,0.615686297417,0.964705884457,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16.338,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":270,"op":330,"st":270,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 26d6875..b4e1b66 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -577,6 +577,9 @@
<!-- Whether to show the side fps hint while on bouncer -->
<bool name="config_show_sidefps_hint_on_bouncer">true</bool>
+ <!-- Max number of lines we want to show for the bouncer secondary message -->
+ <integer name="bouncer_secondary_message_lines">2</integer>
+
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 64615fb..7187de8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -807,6 +807,7 @@
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
+ <dimen name="secondary_message_padding">8dp</dimen>
<dimen name="keyguard_security_container_padding_top">20dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index a82f0e3..0842953 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -22,8 +22,6 @@
import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.ColorStateList
-import android.content.res.TypedArray
-import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import com.android.app.animation.Interpolators
@@ -41,12 +39,28 @@
protected open val SHOW_DURATION_MILLIS = 150L
protected open val HIDE_DURATION_MILLIS = 200L
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ mDefaultColorState = getColorInStyle()
+ }
+
+ private fun getColorInStyle(): ColorStateList? {
+ val styledAttributes =
+ context.obtainStyledAttributes(styleResId, intArrayOf(android.R.attr.textColor))
+ var colorStateList: ColorStateList? = null
+ if (styledAttributes != null) {
+ colorStateList = styledAttributes.getColorStateList(0)
+ }
+ styledAttributes.recycle()
+ return colorStateList
+ }
+
override fun updateTextColor() {
var colorState = mDefaultColorState
mNextMessageColorState?.defaultColor?.let { color ->
if (color != DEFAULT_COLOR) {
colorState = mNextMessageColorState
- mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR)
+ mNextMessageColorState = mDefaultColorState ?: ColorStateList.valueOf(DEFAULT_COLOR)
}
}
setTextColor(colorState)
@@ -57,15 +71,12 @@
}
override fun onThemeChanged() {
- val array: TypedArray = mContext.obtainStyledAttributes(intArrayOf(TITLE))
- val newTextColors: ColorStateList = ColorStateList.valueOf(array.getColor(0, Color.RED))
- array.recycle()
- mDefaultColorState = newTextColors
+ mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
super.onThemeChanged()
}
override fun reloadColor() {
- mDefaultColorState = Utils.getColorAttr(context, TITLE)
+ mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
super.reloadColor()
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b153785..1f75e81 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -16,6 +16,11 @@
package com.android.keyguard;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
+
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -32,6 +37,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.settingslib.WirelessUtils;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
@@ -40,6 +46,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
import com.android.systemui.telephony.TelephonyListenerManager;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -68,6 +75,7 @@
private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
@VisibleForTesting
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierTextManagerLogger mLogger;
private final WifiRepository mWifiRepository;
private final boolean[] mSimErrorState;
private final int mSimSlotsNumber;
@@ -97,19 +105,13 @@
protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onRefreshCarrierInfo() {
- if (DEBUG) {
- Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
- + Boolean.toString(mTelephonyCapable));
- }
+ mLogger.logUpdateCarrierTextForReason(REASON_REFRESH_CARRIER_INFO);
updateCarrierText();
}
@Override
public void onTelephonyCapable(boolean capable) {
- if (DEBUG) {
- Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
- + Boolean.toString(capable));
- }
+ mLogger.logUpdateCarrierTextForReason(REASON_ON_TELEPHONY_CAPABLE);
mTelephonyCapable = capable;
updateCarrierText();
}
@@ -121,7 +123,7 @@
return;
}
- if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+ mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED);
if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
mSimErrorState[slotId] = true;
updateCarrierText();
@@ -137,6 +139,7 @@
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+ mLogger.logUpdateCarrierTextForReason(REASON_ACTIVE_DATA_SUB_CHANGED);
updateCarrierText();
}
}
@@ -175,7 +178,9 @@
WakefulnessLifecycle wakefulnessLifecycle,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ CarrierTextManagerLogger logger) {
+
mContext = context;
mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
@@ -191,6 +196,7 @@
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLogger = logger;
mBgExecutor.execute(() -> {
boolean supported = mContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
@@ -315,7 +321,7 @@
subOrderBySlot[i] = -1;
}
final CharSequence[] carrierNames = new CharSequence[numSubs];
- if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+ mLogger.logUpdate(numSubs);
for (int i = 0; i < numSubs; i++) {
int subId = subs.get(i).getSubscriptionId();
@@ -325,9 +331,7 @@
int simState = mKeyguardUpdateMonitor.getSimState(subId);
CharSequence carrierName = subs.get(i).getCarrierName();
CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
- if (DEBUG) {
- Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
- }
+ mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName));
if (carrierTextForSimState != null) {
allSimsMissing = false;
carrierNames[i] = carrierTextForSimState;
@@ -340,9 +344,7 @@
// Wi-Fi is disassociated or disabled
if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
|| mWifiRepository.isWifiConnectedWithValidSsid()) {
- if (DEBUG) {
- Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
- }
+ mLogger.logUpdateWfcCheck();
anySimReadyAndInService = true;
}
}
@@ -379,7 +381,7 @@
if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
}
- if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+ mLogger.logUpdateFromStickyBroadcast(plmn, spn);
if (Objects.equals(plmn, spn)) {
text = plmn;
} else {
@@ -409,6 +411,7 @@
!allSimsMissing,
subsIds,
airplaneMode);
+ mLogger.logCallbackSentFromUpdate(info);
postToCallback(info);
Trace.endSection();
}
@@ -645,6 +648,7 @@
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierTextManagerLogger mLogger;
private boolean mShowAirplaneMode;
private boolean mShowMissingSim;
@@ -658,7 +662,8 @@
WakefulnessLifecycle wakefulnessLifecycle,
@Main Executor mainExecutor,
@Background Executor bgExecutor,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ CarrierTextManagerLogger logger) {
mContext = context;
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
@@ -669,6 +674,7 @@
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLogger = logger;
}
/** */
@@ -688,7 +694,7 @@
return new CarrierTextManager(
mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
- mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+ mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
}
}
/**
@@ -716,6 +722,17 @@
this.subscriptionIds = subscriptionIds;
this.airplaneMode = airplaneMode;
}
+
+ @Override
+ public String toString() {
+ return "CarrierTextCallbackInfo{"
+ + "carrierText=" + carrierText
+ + ", listOfCarriers=" + Arrays.toString(listOfCarriers)
+ + ", anySimReady=" + anySimReady
+ + ", subscriptionIds=" + Arrays.toString(subscriptionIds)
+ + ", airplaneMode=" + airplaneMode
+ + '}';
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index b18abf3..c6d1471 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -22,6 +22,8 @@
import android.view.KeyEvent;
import android.view.View;
+import androidx.annotation.CallSuper;
+
import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
@@ -48,7 +50,9 @@
protected abstract void resetState();
@Override
+ @CallSuper
protected void onFinishInflate() {
+ super.onFinishInflate();
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a229b13..e057188 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -37,6 +37,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
import java.util.HashMap;
import java.util.Map;
@@ -77,9 +78,10 @@
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
- EmergencyButtonController emergencyButtonController) {
+ EmergencyButtonController emergencyButtonController,
+ FeatureFlags featureFlags) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
- messageAreaControllerFactory);
+ messageAreaControllerFactory, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
new file mode 100644
index 0000000..d821d30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockFrame.kt
@@ -0,0 +1,39 @@
+package com.android.keyguard
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+
+class KeyguardClockFrame(
+ context: Context,
+ attrs: AttributeSet,
+) : FrameLayout(context, attrs) {
+ private var drawAlpha: Int = 255
+
+ protected override fun onSetAlpha(alpha: Int): Boolean {
+ drawAlpha = alpha
+ return true
+ }
+
+ protected override fun dispatchDraw(canvas: Canvas) {
+ val restoreTo = saveCanvasAlpha(this, canvas, drawAlpha)
+ super.dispatchDraw(canvas)
+ canvas.restoreToCount(restoreTo)
+ }
+
+ companion object {
+ @JvmStatic
+ fun saveCanvasAlpha(view: View, canvas: Canvas, alpha: Int): Int {
+ var (x, y) =
+ run {
+ val locationOnScreen = IntArray(2)
+ view.getLocationOnScreen(locationOnScreen)
+ Pair(locationOnScreen[0].toFloat(), locationOnScreen[1].toFloat())
+ }
+
+ return canvas.saveLayerAlpha(-1f * x, -1f * y, x + view.width, y + view.height, alpha)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 644a9bc..05a8f9f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,11 +5,11 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
+import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
@@ -69,12 +69,13 @@
/**
* Frame for small/large clocks
*/
- private FrameLayout mSmallClockFrame;
- private FrameLayout mLargeClockFrame;
+ private KeyguardClockFrame mSmallClockFrame;
+ private KeyguardClockFrame mLargeClockFrame;
private ClockController mClock;
private View mStatusArea;
private int mSmartspaceTopOffset;
+ private int mDrawAlpha = 255;
/**
* Maintain state so that a newly connected plugin can be initialized.
@@ -121,6 +122,19 @@
onDensityOrFontScaleChanged();
}
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ mDrawAlpha = alpha;
+ return true;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int restoreTo = KeyguardClockFrame.saveCanvasAlpha(this, canvas, mDrawAlpha);
+ super.dispatchDraw(canvas);
+ canvas.restoreToCount(restoreTo);
+ }
+
public void setLogBuffer(LogBuffer logBuffer) {
mLogBuffer = logBuffer;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 676f342..d8bf570 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -179,20 +179,6 @@
}
/**
- * Set alpha directly to mView will clip clock, so we set alpha to clock face instead
- */
- public void setAlpha(float alpha) {
- ClockController clock = getClock();
- if (clock != null) {
- clock.getLargeClock().getView().setAlpha(alpha);
- clock.getSmallClock().getView().setAlpha(alpha);
- }
- if (mStatusArea != null) {
- mStatusArea.setAlpha(alpha);
- }
- }
-
- /**
* Attach the controller to the view it relates to.
*/
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index 7eae729..c9d9069 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -21,11 +21,14 @@
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
+import com.android.systemui.R;
/**
* A Base class for all Keyguard password/pattern/pin related inputs.
@@ -33,6 +36,9 @@
public abstract class KeyguardInputView extends LinearLayout {
private Runnable mOnFinishImeAnimationRunnable;
+ @Nullable
+ private View mBouncerMessageView;
+
public KeyguardInputView(Context context) {
super(context);
}
@@ -87,6 +93,18 @@
mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
}
+ @Override
+ @CallSuper
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBouncerMessageView = findViewById(R.id.bouncer_message_view);
+ }
+
+ @Nullable
+ public final View getBouncerMessageView() {
+ return mBouncerMessageView;
+ }
+
public void runOnFinishImeAnimationRunnable() {
if (mOnFinishImeAnimationRunnable != null) {
mOnFinishImeAnimationRunnable.run();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index a0f5f34..3c05299 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -31,6 +32,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -53,16 +55,19 @@
// (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
// state for the current security method.
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
+ private final FeatureFlags mFeatureFlags;
protected KeyguardInputViewController(T view, SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback,
EmergencyButtonController emergencyButtonController,
- @Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
+ @Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
+ FeatureFlags featureFlags) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
mEmergencyButtonController = emergencyButtonController;
+ mFeatureFlags = featureFlags;
if (messageAreaControllerFactory != null) {
try {
BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
@@ -82,9 +87,21 @@
}
@Override
+ @CallSuper
protected void onViewAttached() {
+ updateMessageAreaVisibility();
}
+ private void updateMessageAreaVisibility() {
+ if (mMessageAreaController == null) return;
+ if (mFeatureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
+ mMessageAreaController.disable();
+ } else {
+ mMessageAreaController.setIsVisible(true);
+ }
+ }
+
+
@Override
protected void onViewDetached() {
}
@@ -208,14 +225,14 @@
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
emergencyButtonController, mMessageAreaControllerFactory,
- mDevicePostureController);
+ mDevicePostureController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
- mFalsingCollector, mKeyguardViewController);
-
+ mFalsingCollector, mKeyguardViewController,
+ mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
@@ -227,13 +244,13 @@
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController);
+ emergencyButtonController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController);
+ emergencyButtonController, mFeatureFlags);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
index 553453d..fc66527 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageArea.java
@@ -44,11 +44,18 @@
private ViewGroup mContainer;
private int mTopMargin;
protected boolean mAnimate;
+ private final int mStyleResId;
+ private boolean mIsDisabled = false;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
-
+ if (attrs != null) {
+ mStyleResId = attrs.getStyleAttribute();
+ } else {
+ // Set to default reference style if the component is used without setting "style" attr
+ mStyleResId = R.style.Keyguard_TextView;
+ }
onThemeChanged();
}
@@ -82,13 +89,17 @@
}
void onDensityOrFontScaleChanged() {
- TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
+ TypedArray array = mContext.obtainStyledAttributes(getStyleResId(), new int[] {
android.R.attr.textSize
});
setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0));
array.recycle();
}
+ protected int getStyleResId() {
+ return mStyleResId;
+ }
+
@Override
public void setMessage(CharSequence msg, boolean animate) {
if (!TextUtils.isEmpty(msg)) {
@@ -118,6 +129,10 @@
}
void update() {
+ if (mIsDisabled) {
+ setVisibility(GONE);
+ return;
+ }
CharSequence status = mMessage;
setVisibility(TextUtils.isEmpty(status) || (!mIsVisible) ? INVISIBLE : VISIBLE);
setText(status);
@@ -136,4 +151,17 @@
/** Set the text color */
protected abstract void updateTextColor();
+
+ /**
+ * Mark this view with {@link android.view.View#GONE} visibility to remove this from the layout
+ * of the view. Any calls to {@link #setIsVisible(boolean)} after this will be a no-op.
+ */
+ public void disable() {
+ mIsDisabled = true;
+ update();
+ }
+
+ public boolean isDisabled() {
+ return mIsDisabled;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 99bc32a..0332c9f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -133,6 +133,14 @@
mView.setIsVisible(isVisible);
}
+ /**
+ * Mark this view with {@link View#GONE} visibility to remove this from the layout of the view.
+ * Any calls to {@link #setIsVisible(boolean)} after this will be a no-op.
+ */
+ public void disable() {
+ mView.disable();
+ }
+
public void setMessage(CharSequence s) {
setMessage(s, true);
}
@@ -141,6 +149,9 @@
* Sets a message to the underlying text view.
*/
public void setMessage(CharSequence s, boolean animate) {
+ if (mView.isDisabled()) {
+ return;
+ }
mView.setMessage(s, animate);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 58807e4..2377057 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -51,7 +51,7 @@
private View[][] mViews;
private int mYTrans;
private int mYTransOffset;
- private View mBouncerMessageView;
+ private View mBouncerMessageArea;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public static final long ANIMATION_DURATION = 650;
@@ -145,7 +145,7 @@
super.onFinishInflate();
mContainer = findViewById(R.id.pin_container);
- mBouncerMessageView = findViewById(R.id.bouncer_message_area);
+ mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
mViews = new View[][]{
new View[]{
findViewById(R.id.row0), null, null
@@ -221,9 +221,9 @@
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
float standardProgress = standardDecelerate.getInterpolation(progress);
- mBouncerMessageView.setTranslationY(
+ mBouncerMessageArea.setTranslationY(
mYTrans - mYTrans * standardProgress);
- mBouncerMessageView.setAlpha(standardProgress);
+ mBouncerMessageArea.setAlpha(standardProgress);
for (int i = 0; i < mViews.length; i++) {
View[] row = mViews[i];
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index d221e22..1f6b09b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -40,6 +40,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -104,10 +105,11 @@
@Main DelayableExecutor mainExecutor,
@Main Resources resources,
FalsingCollector falsingCollector,
- KeyguardViewController keyguardViewController) {
+ KeyguardViewController keyguardViewController,
+ FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController);
+ emergencyButtonController, featureFlags);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 39225fb..64b1c50 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -38,6 +38,7 @@
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.policy.DevicePostureController;
import java.util.HashMap;
@@ -196,9 +197,9 @@
FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- DevicePostureController postureController) {
+ DevicePostureController postureController, FeatureFlags featureFlags) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
- messageAreaControllerFactory);
+ messageAreaControllerFactory, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 2339747..5cb2c5c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -34,6 +34,8 @@
import android.view.KeyEvent;
import android.view.View;
+import androidx.annotation.CallSuper;
+
import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
@@ -147,7 +149,9 @@
}
@Override
+ @CallSuper
protected void onFinishInflate() {
+ super.onFinishInflate();
mPasswordEntry = findViewById(getPasswordTextViewId());
// Set selected property on so the view can send accessibility events.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index ded1238..31cbdde 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -27,6 +27,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
@@ -58,10 +59,11 @@
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
- FalsingCollector falsingCollector) {
+ FalsingCollector falsingCollector,
+ FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
- emergencyButtonController);
+ emergencyButtonController, featureFlags);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 1adaafb..f191281 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -56,7 +56,7 @@
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector);
+ emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
index 3fc39af..1461dbe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimInputView.kt
@@ -30,6 +30,7 @@
private var disableESimButton: KeyguardEsimArea? = null
override fun onFinishInflate() {
+ super.onFinishInflate()
simImageView = findViewById(R.id.keyguard_sim)
disableESimButton = findViewById(R.id.keyguard_esim_area)
super.onFinishInflate()
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index a16f3047..61280af 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -41,6 +41,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -81,10 +82,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- EmergencyButtonController emergencyButtonController) {
+ EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector);
+ emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index e9405eb..49d786f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -38,6 +38,7 @@
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -85,10 +86,10 @@
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
- EmergencyButtonController emergencyButtonController) {
+ EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector);
+ emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusContainer.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusContainer.kt
new file mode 100644
index 0000000..d885892
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusContainer.kt
@@ -0,0 +1,24 @@
+package com.android.keyguard
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.widget.LinearLayout
+
+class KeyguardStatusContainer(
+ context: Context,
+ attrs: AttributeSet,
+) : LinearLayout(context, attrs) {
+ private var drawAlpha: Int = 255
+
+ protected override fun onSetAlpha(alpha: Int): Boolean {
+ drawAlpha = alpha
+ return true
+ }
+
+ protected override fun dispatchDraw(canvas: Canvas) {
+ val restoreTo = KeyguardClockFrame.saveCanvasAlpha(this, canvas, drawAlpha)
+ super.dispatchDraw(canvas)
+ canvas.restoreToCount(restoreTo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2313609..553af5b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,6 +19,7 @@
import static java.util.Collections.emptySet;
import android.content.Context;
+import android.graphics.Canvas;
import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
@@ -47,6 +48,7 @@
private KeyguardSliceView mKeyguardSlice;
private View mMediaHostContainer;
+ private int mDrawAlpha = 255;
private float mDarkAmount = 0;
public KeyguardStatusView(Context context) {
@@ -136,30 +138,16 @@
Trace.endSection();
}
- /**
- * Clock content will be clipped when goes beyond bounds,
- * so we setAlpha for all views except clock
- */
- public void setAlpha(float alpha, boolean excludeClock) {
- if (!excludeClock) {
- setAlpha(alpha);
- return;
- }
- if (alpha == 1 || alpha == 0) {
- setAlpha(alpha);
- }
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child == mStatusViewContainer) {
- for (int j = 0; j < mStatusViewContainer.getChildCount(); j++) {
- View innerChild = mStatusViewContainer.getChildAt(j);
- if (innerChild != mClockView) {
- innerChild.setAlpha(alpha);
- }
- }
- } else {
- child.setAlpha(alpha);
- }
- }
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ mDrawAlpha = alpha;
+ return true;
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ int restoreTo = KeyguardClockFrame.saveCanvasAlpha(this, canvas, mDrawAlpha);
+ super.dispatchDraw(canvas);
+ canvas.restoreToCount(restoreTo);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index af47466..794eeda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -180,8 +180,7 @@
*/
public void setAlpha(float alpha) {
if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
- mView.setAlpha(alpha, true);
- mKeyguardClockSwitchController.setAlpha(alpha);
+ mView.setAlpha(alpha);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0d4b543..ea2b385 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1460,6 +1460,14 @@
ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
handleFaceError(error.getMsgId(), error.getMsg());
} else if (status instanceof FailedAuthenticationStatus) {
+ if (isFaceLockedOut()) {
+ // TODO b/270090188: remove this hack when biometrics fixes this issue.
+ // FailedAuthenticationStatus is emitted after ErrorAuthenticationStatus
+ // for lockout error is received
+ mLogger.d("onAuthenticationFailed called after"
+ + " face has been locked out");
+ return;
+ }
handleFaceAuthFailed();
} else if (status instanceof HelpAuthenticationStatus) {
HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
@@ -1968,6 +1976,13 @@
@Override
public void onAuthenticationFailed() {
+ if (isFaceLockedOut()) {
+ // TODO b/270090188: remove this hack when biometrics fixes this issue.
+ // onAuthenticationFailed is called after onAuthenticationError
+ // for lockout error is received
+ mLogger.d("onAuthenticationFailed called after face has been locked out");
+ return;
+ }
handleFaceAuthFailed();
}
@@ -2621,6 +2636,14 @@
}
/**
+ * @return true if the FP sensor is non-UDFPS and the device can be unlocked using fingerprint
+ * at this moment.
+ */
+ public boolean isFingerprintAllowedInBouncer() {
+ return !isUdfpsSupported() && isUnlockingWithFingerprintAllowed();
+ }
+
+ /**
* @return true if there's at least one sfps enrollment for the current user.
*/
public boolean isSfpsEnrolled() {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
new file mode 100644
index 0000000..4001a4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.keyguard.logging
+
+import androidx.annotation.IntDef
+import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.CarrierTextManagerLog
+import javax.inject.Inject
+
+/** Logger adapter for [CarrierTextManager] to add detailed messages in a [LogBuffer] */
+@SysUISingleton
+class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) {
+ /**
+ * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText
+ */
+ fun logUpdate(numSubs: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { int1 = numSubs },
+ { "updateCarrierText: numSubs=$int1" },
+ )
+ }
+
+ fun logUpdateLoopStart(sub: Int, simState: Int, carrierName: String) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ int1 = sub
+ int2 = simState
+ str1 = carrierName
+ },
+ { "┣ updateCarrierText: updating sub=$int1 simState=$int2 carrierName=$str1" },
+ )
+ }
+
+ fun logUpdateWfcCheck() {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {},
+ { "┣ updateCarrierText: found WFC state" },
+ )
+ }
+
+ fun logUpdateFromStickyBroadcast(plmn: String?, spn: String?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = plmn
+ str2 = spn
+ },
+ { "┣ updateCarrierText: getting PLMN/SPN sticky brdcst. plmn=$str1, spn=$str1" },
+ )
+ }
+
+ /** De-structures the info object so that we don't have to generate new strings */
+ fun logCallbackSentFromUpdate(info: CarrierTextCallbackInfo) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = "${info.carrierText}"
+ bool1 = info.anySimReady
+ bool2 = info.airplaneMode
+ },
+ {
+ "â”— updateCarrierText: " +
+ "result=(carrierText=$str1, anySimReady=$bool1, airplaneMode=$bool2)"
+ },
+ )
+ }
+
+ /**
+ * Used to log the starting point for _why_ the carrier text is updating. In order to keep us
+ * from holding on to too many objects, we'll just use simple ints for reasons here
+ */
+ fun logUpdateCarrierTextForReason(@CarrierTextRefreshReason reason: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { int1 = reason },
+ { "refreshing carrier info for reason: ${reason.reasonMessage()}" }
+ )
+ }
+
+ companion object {
+ const val REASON_REFRESH_CARRIER_INFO = 1
+ const val REASON_ON_TELEPHONY_CAPABLE = 2
+ const val REASON_ON_SIM_STATE_CHANGED = 3
+ const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ value =
+ [
+ REASON_REFRESH_CARRIER_INFO,
+ REASON_ON_TELEPHONY_CAPABLE,
+ REASON_ON_SIM_STATE_CHANGED,
+ REASON_ACTIVE_DATA_SUB_CHANGED,
+ ]
+ )
+ annotation class CarrierTextRefreshReason
+
+ private fun @receiver:CarrierTextRefreshReason Int.reasonMessage() =
+ when (this) {
+ REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO"
+ REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
+ REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED"
+ REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
+ else -> "unknown"
+ }
+ }
+}
+
+private const val TAG = "CarrierTextManagerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 76086df..403c809 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -31,10 +31,10 @@
import android.hardware.biometrics.BiometricSourceType
import android.view.View
import androidx.core.graphics.ColorUtils
+import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
-import com.android.app.animation.Interpolators
import com.android.systemui.biometrics.AuthController
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -63,7 +63,7 @@
private var cameraProtectionColor = Color.BLACK
var faceScanningAnimColor = Utils.getColorAttrDefaultColor(context,
- R.attr.wallpaperTextColorAccent)
+ com.android.internal.R.attr.materialColorPrimaryFixed)
private var cameraProtectionAnimator: ValueAnimator? = null
var hideOverlayRunnable: Runnable? = null
var faceAuthSucceeded = false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 962140f..9007279 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -20,7 +20,6 @@
import android.app.ActivityTaskManager
import android.content.Context
import android.content.res.Configuration
-import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
@@ -436,24 +435,30 @@
fun update() {
val isKeyguard = reason == REASON_AUTH_KEYGUARD
if (isKeyguard) {
- val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
+ val color =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorPrimaryFixed
+ )
+ val outerRimColor =
+ com.android.settingslib.Utils.getColorAttrDefaultColor(
+ context,
+ com.android.internal.R.attr.materialColorPrimaryFixedDim
+ )
val chevronFill =
com.android.settingslib.Utils.getColorAttrDefaultColor(
context,
- android.R.attr.textColorPrimaryInverse
+ com.android.internal.R.attr.materialColorOnPrimaryFixed
)
- for (key in listOf(".blue600", ".blue400")) {
- addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
- }
+ addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
+ }
+ addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
+ PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
}
addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
}
- } else if (!isDarkMode(context)) {
- addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
- PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
- }
} else if (isDarkMode(context)) {
for (key in listOf(".blue600", ".blue400")) {
addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 63f0e9d..a2840fc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -176,9 +176,9 @@
}
mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext,
- android.R.attr.textColorPrimary);
+ com.android.internal.R.attr.materialColorOnSurface);
final int backgroundColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.internal.R.attr.colorSurface);
+ com.android.internal.R.attr.materialColorSurfaceContainerHigh);
mBgProtection.setImageTintList(ColorStateList.valueOf(backgroundColor));
mLockScreenFp.invalidate(); // updated with a valueCallback
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4bdfc7e..340ed2e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -665,15 +665,6 @@
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
- // 2500 - output switcher
- // TODO(b/261538825): Tracking Bug
- @JvmField
- val OUTPUT_SWITCHER_ADVANCED_LAYOUT = releasedFlag(2500, "output_switcher_advanced_layout")
- @JvmField
- val OUTPUT_SWITCHER_ROUTES_PROCESSING = releasedFlag(2501, "output_switcher_routes_processing")
- @JvmField
- val OUTPUT_SWITCHER_DEVICE_STATUS = releasedFlag(2502, "output_switcher_device_status")
-
// 2700 - unfold transitions
// TODO(b/265764985): Tracking Bug
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 6e77dcb..54da680 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -42,8 +42,6 @@
import android.app.Service;
import android.app.WindowConfiguration;
import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -77,6 +75,7 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
@@ -105,7 +104,8 @@
}
}
- private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers) {
+ private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers,
+ SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
for (int i = 0; i < info.getChanges().size(); i++) {
boolean changeIsWallpaper =
@@ -115,32 +115,14 @@
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1;
- boolean isNotInRecents;
- WindowConfiguration windowConfiguration = null;
- if (taskInfo != null) {
- if (taskInfo.getConfiguration() != null) {
- windowConfiguration =
- change.getTaskInfo().getConfiguration().windowConfiguration;
- }
- isNotInRecents = !change.getTaskInfo().isRunning;
- } else {
- isNotInRecents = true;
- }
- Rect localBounds = new Rect(change.getEndAbsBounds());
- localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
- final RemoteAnimationTarget target = new RemoteAnimationTarget(
- taskId,
- newModeToLegacyMode(change.getMode()),
- change.getLeash(),
- (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
- || (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
- null /* clipRect */,
- new Rect(0, 0, 0, 0) /* contentInsets */,
+ final RemoteAnimationTarget target = TransitionUtil.newTarget(change,
+ // wallpapers go into the "below" layer space
info.getChanges().size() - i,
- new Point(), localBounds, new Rect(change.getEndAbsBounds()),
- windowConfiguration, isNotInRecents, null /* startLeash */,
- change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */);
+ // keyguard treats wallpaper as translucent
+ (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
+ info, t, leashMap);
+
// Use hasAnimatingParent to mark the anything below root task
if (taskId != -1 && change.getParent() != null) {
final TransitionInfo.Change parentChange = info.getChange(change.getParent());
@@ -178,31 +160,27 @@
return new IRemoteTransition.Stub() {
final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks =
new ArrayMap<>();
+ private final ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = new ArrayMap<>();
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
throws RemoteException {
Slog.d(TAG, "Starts IRemoteAnimationRunner: info=" + info);
- final RemoteAnimationTarget[] apps = wrap(info, false /* wallpapers */);
- final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
+ final RemoteAnimationTarget[] apps =
+ wrap(info, false /* wallpapers */, t, mLeashMap);
+ final RemoteAnimationTarget[] wallpapers =
+ wrap(info, true /* wallpapers */, t, mLeashMap);
final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
// Sets the alpha to 0 for the opening root task for fade in animation. And since
// the fade in animation can only apply on the first opening app, so set alpha to 1
// for anything else.
- boolean foundOpening = false;
for (RemoteAnimationTarget target : apps) {
if (target.taskId != -1
&& target.mode == RemoteAnimationTarget.MODE_OPENING
&& !target.hasAnimatingParent) {
- if (foundOpening) {
- Log.w(TAG, "More than one opening target");
- t.setAlpha(target.leash, 1.0f);
- continue;
- }
t.setAlpha(target.leash, 0.0f);
- foundOpening = true;
} else {
t.setAlpha(target.leash, 1.0f);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
index f4145db..4085dab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardBouncerMessages.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactory.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.keyguard.bouncer.data.factory
import android.annotation.IntDef
+import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
@@ -34,6 +35,7 @@
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.bouncer_face_not_recognized
import com.android.systemui.R.string.keyguard_enter_password
import com.android.systemui.R.string.keyguard_enter_pattern
@@ -71,10 +73,86 @@
import com.android.systemui.R.string.kg_wrong_password_try_again
import com.android.systemui.R.string.kg_wrong_pattern_try_again
import com.android.systemui.R.string.kg_wrong_pin_try_again
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.bouncer.shared.model.Message
+import javax.inject.Inject
-typealias BouncerMessage = Pair<Int, Int>
+@SysUISingleton
+class BouncerMessageFactory
+@Inject
+constructor(
+ private val updateMonitor: KeyguardUpdateMonitor,
+ private val securityModel: KeyguardSecurityModel,
+) {
-fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
+ fun createFromPromptReason(
+ @BouncerPromptReason reason: Int,
+ userId: Int,
+ ): BouncerMessageModel? {
+ val pair =
+ getBouncerMessage(
+ reason,
+ securityModel.getSecurityMode(userId),
+ updateMonitor.isFingerprintAllowedInBouncer
+ )
+ return pair?.let {
+ BouncerMessageModel(
+ message = Message(messageResId = pair.first),
+ secondaryMessage = Message(messageResId = pair.second)
+ )
+ }
+ }
+
+ fun createFromString(
+ primaryMsg: String? = null,
+ secondaryMsg: String? = null
+ ): BouncerMessageModel =
+ BouncerMessageModel(
+ message = primaryMsg?.let { Message(message = it) },
+ secondaryMessage = secondaryMsg?.let { Message(message = it) },
+ )
+
+ /**
+ * Helper method that provides the relevant bouncer message that should be shown for different
+ * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
+ * provide a more specific message.
+ */
+ private fun getBouncerMessage(
+ @BouncerPromptReason reason: Int,
+ securityMode: SecurityMode,
+ fpAllowedInBouncer: Boolean = false
+ ): Pair<Int, Int>? {
+ return when (reason) {
+ PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
+ PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
+ PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
+ PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
+ PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
+ PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
+ PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
+ PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
+ if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
+ else incorrectSecurityInput(securityMode)
+ PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
+ if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
+ else nonStrongAuthTimeout(securityMode)
+ PROMPT_REASON_TRUSTAGENT_EXPIRED ->
+ if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
+ else trustAgentDisabled(securityMode)
+ PROMPT_REASON_INCORRECT_FACE_INPUT ->
+ if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
+ else incorrectFaceInput(securityMode)
+ PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
+ PROMPT_REASON_DEFAULT ->
+ if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
+ else defaultMessage(securityMode)
+ PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
+ else -> null
+ }
+ }
+}
@Retention(AnnotationRetention.SOURCE)
@IntDef(
@@ -97,48 +175,7 @@
)
annotation class BouncerPromptReason
-/**
- * Helper method that provides the relevant bouncer message that should be shown for different
- * scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
- * provide a more specific message.
- */
-@JvmOverloads
-fun getBouncerMessage(
- @BouncerPromptReason reason: Int,
- securityMode: SecurityMode,
- fpAllowedInBouncer: Boolean = false
-): BouncerMessage {
- return when (reason) {
- PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
- PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
- PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
- PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
- PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
- PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
- PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
- PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
- PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
- if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
- else incorrectSecurityInput(securityMode)
- PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
- if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
- else nonStrongAuthTimeout(securityMode)
- PROMPT_REASON_TRUSTAGENT_EXPIRED ->
- if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
- else trustAgentDisabled(securityMode)
- PROMPT_REASON_INCORRECT_FACE_INPUT ->
- if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
- else incorrectFaceInput(securityMode)
- PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
- PROMPT_REASON_DEFAULT ->
- if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
- else defaultMessage(securityMode)
- PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
- else -> emptyBouncerMessage()
- }
-}
-
-fun defaultMessage(securityMode: SecurityMode): BouncerMessage {
+private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
SecurityMode.Password -> Pair(keyguard_enter_password, 0)
@@ -147,7 +184,7 @@
}
}
-fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
@@ -156,7 +193,7 @@
}
}
-fun incorrectSecurityInput(securityMode: SecurityMode): BouncerMessage {
+private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
@@ -165,7 +202,7 @@
}
}
-fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessage {
+private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
@@ -174,7 +211,7 @@
}
}
-fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessage {
+private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
@@ -183,7 +220,7 @@
}
}
-fun incorrectFaceInput(securityMode: SecurityMode): BouncerMessage {
+private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
@@ -192,7 +229,7 @@
}
}
-fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
@@ -201,7 +238,7 @@
}
}
-fun biometricLockout(securityMode: SecurityMode): BouncerMessage {
+private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
@@ -210,7 +247,7 @@
}
}
-fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessage {
+private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
@@ -219,7 +256,7 @@
}
}
-fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessage {
+private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
@@ -228,7 +265,7 @@
}
}
-fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessage {
+private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
SecurityMode.Password ->
@@ -238,7 +275,7 @@
}
}
-fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessage {
+private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
@@ -247,7 +284,7 @@
}
}
-fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
@@ -256,7 +293,7 @@
}
}
-fun nonStrongAuthTimeout(securityMode: SecurityMode): BouncerMessage {
+private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
@@ -265,7 +302,7 @@
}
}
-fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
@@ -274,7 +311,7 @@
}
}
-fun faceUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
@@ -283,7 +320,7 @@
}
}
-fun fingerprintUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
+private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
@@ -292,7 +329,7 @@
}
}
-fun trustAgentDisabled(securityMode: SecurityMode): BouncerMessage {
+private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
@@ -301,7 +338,7 @@
}
}
-fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
+private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
@@ -310,7 +347,7 @@
}
}
-fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessage {
+private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern ->
Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
new file mode 100644
index 0000000..c4400bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepository.kt
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.data.repository
+
+import android.hardware.biometrics.BiometricSourceType
+import android.hardware.biometrics.BiometricSourceType.FACE
+import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/** Provide different sources of messages that needs to be shown on the bouncer. */
+interface BouncerMessageRepository {
+ /**
+ * Messages that are shown in response to the incorrect security attempts on the bouncer and
+ * primary authentication method being locked out, along with countdown messages before primary
+ * auth is active again.
+ */
+ val primaryAuthMessage: Flow<BouncerMessageModel?>
+
+ /**
+ * Help messages that are shown to the user on how to successfully perform authentication using
+ * face.
+ */
+ val faceAcquisitionMessage: Flow<BouncerMessageModel?>
+
+ /**
+ * Help messages that are shown to the user on how to successfully perform authentication using
+ * fingerprint.
+ */
+ val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
+
+ /** Custom message that is displayed when the bouncer is being shown to launch an app. */
+ val customMessage: Flow<BouncerMessageModel?>
+
+ /**
+ * Messages that are shown in response to biometric authentication attempts through face or
+ * fingerprint.
+ */
+ val biometricAuthMessage: Flow<BouncerMessageModel?>
+
+ /** Messages that are shown when certain auth flags are set. */
+ val authFlagsMessage: Flow<BouncerMessageModel?>
+
+ /** Messages that are show after biometrics are locked out temporarily or permanently */
+ val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+
+ /** Set the value for [primaryAuthMessage] */
+ fun setPrimaryAuthMessage(value: BouncerMessageModel?)
+
+ /** Set the value for [faceAcquisitionMessage] */
+ fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
+ /** Set the value for [fingerprintAcquisitionMessage] */
+ fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
+
+ /** Set the value for [customMessage] */
+ fun setCustomMessage(value: BouncerMessageModel?)
+
+ /**
+ * Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
+ * [fingerprintAcquisitionMessage] & [customMessage]
+ */
+ fun clearMessage()
+}
+
+@SysUISingleton
+class BouncerMessageRepositoryImpl
+@Inject
+constructor(
+ trustRepository: TrustRepository,
+ biometricSettingsRepository: BiometricSettingsRepository,
+ updateMonitor: KeyguardUpdateMonitor,
+ private val bouncerMessageFactory: BouncerMessageFactory,
+ private val userRepository: UserRepository,
+ fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+) : BouncerMessageRepository {
+
+ private val isFaceEnrolledAndEnabled =
+ and(
+ biometricSettingsRepository.isFaceAuthenticationEnabled,
+ biometricSettingsRepository.isFaceEnrolled
+ )
+
+ private val isFingerprintEnrolledAndEnabled =
+ and(
+ biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
+ biometricSettingsRepository.isFingerprintEnrolled
+ )
+
+ private val isAnyBiometricsEnabledAndEnrolled =
+ or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
+
+ private val authFlagsBasedPromptReason: Flow<Int> =
+ combine(
+ biometricSettingsRepository.authenticationFlags,
+ trustRepository.isCurrentUserTrustManaged,
+ isAnyBiometricsEnabledAndEnrolled,
+ ::Triple
+ )
+ .map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
+ val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
+ return@map if (
+ trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
+ ) {
+ PROMPT_REASON_RESTART
+ } else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
+ PROMPT_REASON_TIMEOUT
+ } else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
+ PROMPT_REASON_DEVICE_ADMIN
+ } else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
+ PROMPT_REASON_TRUSTAGENT_EXPIRED
+ } else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
+ PROMPT_REASON_TRUSTAGENT_EXPIRED
+ } else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
+ PROMPT_REASON_USER_REQUEST
+ } else if (
+ trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
+ ) {
+ PROMPT_REASON_PREPARE_FOR_UPDATE
+ } else if (
+ trustOrBiometricsAvailable &&
+ flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
+ ) {
+ PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
+ } else {
+ PROMPT_REASON_NONE
+ }
+ }
+
+ private val biometricAuthReason: Flow<Int> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthFailed(
+ biometricSourceType: BiometricSourceType?
+ ) {
+ val promptReason =
+ if (biometricSourceType == FINGERPRINT)
+ PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
+ else if (
+ biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
+ ) {
+ PROMPT_REASON_INCORRECT_FACE_INPUT
+ } else PROMPT_REASON_NONE
+ trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
+ }
+
+ override fun onBiometricsCleared() {
+ trySendWithFailureLogging(
+ PROMPT_REASON_NONE,
+ TAG,
+ "onBiometricsCleared"
+ )
+ }
+
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType?,
+ acquireInfo: Int
+ ) {
+ trySendWithFailureLogging(
+ PROMPT_REASON_NONE,
+ TAG,
+ "clearBiometricPrompt for new auth session."
+ )
+ }
+
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType?,
+ isStrongBiometric: Boolean
+ ) {
+ trySendWithFailureLogging(
+ PROMPT_REASON_NONE,
+ TAG,
+ "onBiometricAuthenticated"
+ )
+ }
+ }
+ updateMonitor.registerCallback(callback)
+ awaitClose { updateMonitor.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
+ private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
+
+ private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
+
+ private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
+ _fingerprintAcquisitionMessage
+
+ private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val customMessage: Flow<BouncerMessageModel?> = _customMessage
+
+ override val biometricAuthMessage: Flow<BouncerMessageModel?> =
+ biometricAuthReason
+ .map {
+ if (it == PROMPT_REASON_NONE) null
+ else
+ bouncerMessageFactory.createFromPromptReason(
+ it,
+ userRepository.getSelectedUserInfo().id
+ )
+ }
+ .onStart { emit(null) }
+ .distinctUntilChanged()
+
+ override val authFlagsMessage: Flow<BouncerMessageModel?> =
+ authFlagsBasedPromptReason
+ .map {
+ if (it == PROMPT_REASON_NONE) null
+ else
+ bouncerMessageFactory.createFromPromptReason(
+ it,
+ userRepository.getSelectedUserInfo().id
+ )
+ }
+ .onStart { emit(null) }
+ .distinctUntilChanged()
+
+ // TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
+ // has been launched.
+ private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
+ if (biometricSourceType == FACE) {
+ trySendWithFailureLogging(
+ updateMonitor.isFaceLockedOut,
+ TAG,
+ "face lock out state changed."
+ )
+ }
+ }
+ }
+ updateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
+ awaitClose { updateMonitor.removeCallback(callback) }
+ }
+
+ override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
+ combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
+ return@combine if (fp) {
+ bouncerMessageFactory.createFromPromptReason(
+ PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
+ userRepository.getSelectedUserInfo().id
+ )
+ } else if (face) {
+ bouncerMessageFactory.createFromPromptReason(
+ PROMPT_REASON_FACE_LOCKED_OUT,
+ userRepository.getSelectedUserInfo().id
+ )
+ } else null
+ }
+
+ override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+ _primaryAuthMessage.value = value
+ }
+
+ override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+ _faceAcquisitionMessage.value = value
+ }
+
+ override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+ _fingerprintAcquisitionMessage.value = value
+ }
+
+ override fun setCustomMessage(value: BouncerMessageModel?) {
+ _customMessage.value = value
+ }
+
+ override fun clearMessage() {
+ _fingerprintAcquisitionMessage.value = null
+ _faceAcquisitionMessage.value = null
+ _primaryAuthMessage.value = null
+ _customMessage.value = null
+ }
+
+ companion object {
+ const val TAG = "BouncerDetailedMessageRepository"
+ }
+}
+
+private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+ flow.combine(anotherFlow) { a, b -> a && b }
+
+private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
+ flow.combine(anotherFlow) { a, b -> a || b }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
new file mode 100644
index 0000000..56f81fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.domain.interactor
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+private val TAG = BouncerMessageAuditLogger::class.simpleName!!
+
+/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
+@SysUISingleton
+class BouncerMessageAuditLogger
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ private val repository: BouncerMessageRepository,
+ private val interactor: BouncerMessageInteractor,
+) : CoreStartable {
+ override fun start() {
+ if (Build.isDebuggable()) {
+ collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
+ collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
+ collectAndLog(repository.customMessage, "customMessage: ")
+ collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
+ collectAndLog(
+ repository.fingerprintAcquisitionMessage,
+ "fingerprintAcquisitionMessage: "
+ )
+ collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
+ collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
+ }
+ }
+
+ private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
+ scope.launch { flow.collect { Log.d(TAG, context + it) } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
new file mode 100644
index 0000000..1754d93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.domain.interactor
+
+import android.os.CountDownTimer
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class BouncerMessageInteractor
+@Inject
+constructor(
+ private val repository: BouncerMessageRepository,
+ private val factory: BouncerMessageFactory,
+ private val userRepository: UserRepository,
+ private val countDownTimerUtil: CountDownTimerUtil,
+ private val featureFlags: FeatureFlags,
+) {
+ fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ val callback =
+ object : CountDownTimerCallback {
+ override fun onFinish() {
+ repository.clearMessage()
+ }
+
+ override fun onTick(millisUntilFinished: Long) {
+ val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
+ val message =
+ factory.createFromPromptReason(
+ reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
+ userId = userRepository.getSelectedUserInfo().id
+ )
+ message?.message?.animate = false
+ message?.message?.formatterArgs =
+ mutableMapOf<String, Any>(Pair("count", secondsRemaining))
+ repository.setPrimaryAuthMessage(message)
+ }
+ }
+ countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
+ }
+
+ fun onPrimaryAuthIncorrectAttempt() {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.setPrimaryAuthMessage(
+ factory.createFromPromptReason(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ userRepository.getSelectedUserInfo().id
+ )
+ )
+ }
+
+ fun setFingerprintAcquisitionMessage(value: String?) {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.setFingerprintAcquisitionMessage(
+ if (value != null) {
+ factory.createFromString(secondaryMsg = value)
+ } else {
+ null
+ }
+ )
+ }
+
+ fun setFaceAcquisitionMessage(value: String?) {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.setFaceAcquisitionMessage(
+ if (value != null) {
+ factory.createFromString(secondaryMsg = value)
+ } else {
+ null
+ }
+ )
+ }
+
+ fun setCustomMessage(value: String?) {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.setCustomMessage(
+ if (value != null) {
+ factory.createFromString(secondaryMsg = value)
+ } else {
+ null
+ }
+ )
+ }
+
+ fun onPrimaryBouncerUserInput() {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.clearMessage()
+ }
+
+ fun onBouncerBeingHidden() {
+ if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
+
+ repository.clearMessage()
+ }
+
+ private fun firstNonNullMessage(
+ oneMessageModel: Flow<BouncerMessageModel?>,
+ anotherMessageModel: Flow<BouncerMessageModel?>
+ ): Flow<BouncerMessageModel?> {
+ return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
+ }
+
+ // Null if feature flag is enabled which gets ignored always or empty bouncer message model that
+ // always maps to an empty string.
+ private fun nullOrEmptyMessage() =
+ flowOf(
+ if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
+ else factory.createFromString("", "")
+ )
+
+ val bouncerMessage =
+ listOf(
+ nullOrEmptyMessage(),
+ repository.primaryAuthMessage,
+ repository.biometricAuthMessage,
+ repository.fingerprintAcquisitionMessage,
+ repository.faceAcquisitionMessage,
+ repository.customMessage,
+ repository.authFlagsMessage,
+ repository.biometricLockedOutMessage,
+ userRepository.selectedUserInfo.map {
+ factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
+ },
+ )
+ .reduce(::firstNonNullMessage)
+ .distinctUntilChanged()
+}
+
+interface CountDownTimerCallback {
+ fun onFinish()
+ fun onTick(millisUntilFinished: Long)
+}
+
+@SysUISingleton
+open class CountDownTimerUtil @Inject constructor() {
+
+ /**
+ * Start a new count down timer that runs for [millisInFuture] with a tick every
+ * [millisInterval]
+ */
+ fun startNewTimer(
+ millisInFuture: Long,
+ millisInterval: Long,
+ callback: CountDownTimerCallback,
+ ): CountDownTimer {
+ return object : CountDownTimer(millisInFuture, millisInterval) {
+ override fun onFinish() = callback.onFinish()
+
+ override fun onTick(millisUntilFinished: Long) =
+ callback.onTick(millisUntilFinished)
+ }
+ .start()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
new file mode 100644
index 0000000..46e8873
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/shared/model/BouncerMessageModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.shared.model
+
+import android.content.res.ColorStateList
+
+/**
+ * Represents the message displayed on the bouncer. It has two parts, primary and a secondary
+ * message
+ */
+data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
+
+/**
+ * Representation of a single message on the bouncer. It can be either a string or a string resource
+ * ID
+ */
+data class Message(
+ val message: String? = null,
+ val messageResId: Int? = null,
+ val colorState: ColorStateList? = null,
+ /** Any plural formatter arguments that can used to format the [messageResId] */
+ var formatterArgs: Map<String, Any>? = null,
+ /** Specifies whether this text should be animated when it is shown. */
+ var animate: Boolean = true,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
new file mode 100644
index 0000000..c0a5a51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.keyguard.BouncerKeyguardMessageArea
+import com.android.keyguard.KeyguardMessageArea
+import com.android.keyguard.KeyguardMessageAreaController
+import com.android.systemui.R
+
+class BouncerMessageView : LinearLayout {
+ constructor(context: Context?) : super(context)
+
+ constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+ init {
+ inflate(context, R.layout.bouncer_message_view, this)
+ }
+
+ var primaryMessageView: BouncerKeyguardMessageArea? = null
+ var secondaryMessageView: BouncerKeyguardMessageArea? = null
+ var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+ var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
+ secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
+ }
+
+ fun init(factory: KeyguardMessageAreaController.Factory) {
+ primaryMessage = factory.create(primaryMessageView)
+ primaryMessage?.init()
+ secondaryMessage = factory.create(secondaryMessageView)
+ secondaryMessage?.init()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 0055f9a..0b6c7c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -25,7 +25,6 @@
import android.os.UserHandle
import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
@@ -36,13 +35,14 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -108,10 +108,14 @@
* lockdown.
*/
val isCurrentUserInLockdown: Flow<Boolean>
+
+ /** Authentication flags set for the current user. */
+ val authenticationFlags: Flow<AuthenticationFlags>
}
-const val TAG = "BiometricsRepositoryImpl"
+private const val TAG = "BiometricsRepositoryImpl"
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BiometricSettingsRepositoryImpl
@Inject
@@ -129,6 +133,8 @@
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
+ private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
+
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
private val strongAuthTracker = StrongAuthTracker(userRepository, context)
@@ -136,6 +142,9 @@
override val isCurrentUserInLockdown: Flow<Boolean> =
strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
+ override val authenticationFlags: Flow<AuthenticationFlags> =
+ strongAuthTracker.currentUserAuthFlags
+
init {
Log.d(TAG, "Registering StrongAuthTracker")
lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
@@ -231,9 +240,14 @@
}
}
+ private val isFaceEnabledByBiometricsManagerForCurrentUser: Flow<Boolean> =
+ userRepository.selectedUserInfo.flatMapLatest { userInfo ->
+ isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
+ }
+
override val isFaceAuthenticationEnabled: Flow<Boolean>
get() =
- combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
+ combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
@@ -249,13 +263,13 @@
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
- private val isFaceEnabledByBiometricsManager =
+ private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
conflatedCallbackFlow {
val callback =
object : IBiometricEnabledOnKeyguardCallback.Stub() {
override fun onChanged(enabled: Boolean, userId: Int) {
trySendWithFailureLogging(
- enabled,
+ Pair(userId, enabled),
TAG,
"biometricsEnabled state changed"
)
@@ -264,9 +278,10 @@
biometricManager?.registerEnabledOnKeyguardCallback(callback)
awaitClose {}
}
+ .onEach { biometricsEnabledForUser[it.first] = it.second }
// This is because the callback is binder-based and we want to avoid multiple callbacks
// being registered.
- .stateIn(scope, SharingStarted.Eagerly, false)
+ .stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
override val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -306,14 +321,13 @@
)
}
+@OptIn(ExperimentalCoroutinesApi::class)
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
LockPatternUtils.StrongAuthTracker(context) {
// Backing field for onStrongAuthRequiredChanged
- private val _strongAuthFlags =
- MutableStateFlow(
- StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
- )
+ private val _authFlags =
+ MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
// Backing field for onIsNonStrongBiometricAllowedChanged
private val _nonStrongBiometricAllowed =
@@ -321,17 +335,15 @@
Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
)
- val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
+ val currentUserAuthFlags: Flow<AuthenticationFlags> =
userRepository.selectedUserInfo
.map { it.id }
.distinctUntilChanged()
.flatMapLatest { userId ->
- _strongAuthFlags
- .filter { it.userId == userId }
+ _authFlags
+ .map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
- .onStart {
- emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
- }
+ .onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
}
/** isStrongBiometricAllowed for the current user. */
@@ -356,7 +368,7 @@
override fun onStrongAuthRequiredChanged(userId: Int) {
val newFlags = getStrongAuthForUser(userId)
- _strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
+ _authFlags.value = AuthenticationFlags(userId, newFlags)
Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
}
@@ -375,11 +387,3 @@
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
-
-private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) {
- val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN)
-}
-
-private fun containsFlag(haystack: Int, needle: Int): Boolean {
- return haystack and needle != 0
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 7c14280..5d15e69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -22,7 +22,7 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
-import com.android.systemui.log.dagger.BouncerLog
+import com.android.systemui.log.dagger.BouncerTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.util.time.SystemClock
@@ -105,7 +105,7 @@
constructor(
private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
- @BouncerLog private val buffer: TableLogBuffer,
+ @BouncerTableLog private val buffer: TableLogBuffer,
) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerShow = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index e7b9af6..4055fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -16,8 +16,14 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.CoreStartable
+import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepositoryImpl
+import com.android.systemui.keyguard.bouncer.domain.interactor.BouncerMessageAuditLogger
import dagger.Binds
import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
interface KeyguardRepositoryModule {
@@ -46,5 +52,13 @@
@Binds
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
+ @Binds
+ fun bouncerMessageRepository(impl: BouncerMessageRepositoryImpl): BouncerMessageRepository
+
+ @Binds
+ @IntoMap
+ @ClassKey(BouncerMessageAuditLogger::class)
+ fun bind(impl: BouncerMessageAuditLogger): CoreStartable
+
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
new file mode 100644
index 0000000..cf5b88f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AuthenticationFlags.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+import com.android.internal.widget.LockPatternUtils
+
+/** Authentication flags corresponding to a user. */
+data class AuthenticationFlags(val userId: Int, val flag: Int) {
+ val isInUserLockdown =
+ containsFlag(
+ flag,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+ )
+
+ val isPrimaryAuthRequiredAfterReboot =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT)
+
+ val isPrimaryAuthRequiredAfterTimeout =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+
+ val isPrimaryAuthRequiredAfterDpmLockdown =
+ containsFlag(
+ flag,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
+ )
+
+ val someAuthRequiredAfterUserRequest =
+ containsFlag(flag, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST)
+
+ val someAuthRequiredAfterTrustAgentExpired =
+ containsFlag(
+ flag,
+ LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
+ )
+
+ val primaryAuthRequiredForUnattendedUpdate =
+ containsFlag(
+ flag,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+ )
+
+ /** Either Class 3 biometrics or primary auth can be used to unlock the device. */
+ val strongerAuthRequiredAfterNonStrongBiometricsTimeout =
+ containsFlag(
+ flag,
+ LockPatternUtils.StrongAuthTracker
+ .STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+ )
+}
+
+private fun containsFlag(haystack: Int, needle: Int): Boolean {
+ return haystack and needle != 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
index c8bd958..9e7dec4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
@@ -38,7 +38,8 @@
object FailedAuthenticationStatus : AuthenticationStatus()
/** Face authentication error message */
-data class ErrorAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() {
+data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
+ AuthenticationStatus() {
/**
* Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out.
diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
new file mode 100644
index 0000000..3be4499
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.log
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.log.dagger.BouncerLog
+import javax.inject.Inject
+
+private const val TAG = "BouncerLog"
+
+/**
+ * Helper class for logging for classes in the [com.android.systemui.keyguard.bouncer] package.
+ *
+ * To enable logcat echoing for an entire buffer:
+ * ```
+ * adb shell settings put global systemui/buffer/BouncerLog <logLevel>
+ *
+ * ```
+ */
+@SysUISingleton
+class BouncerLogger @Inject constructor(@BouncerLog private val buffer: LogBuffer) {
+ fun startBouncerMessageInteractor() {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ "Starting BouncerMessageInteractor.bouncerMessage collector"
+ )
+ }
+
+ fun bouncerMessageUpdated(bouncerMsg: BouncerMessageModel?) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = bouncerMsg?.message?.messageResId ?: -1
+ str1 = bouncerMsg?.message?.message
+ int2 = bouncerMsg?.secondaryMessage?.messageResId ?: -1
+ str2 = bouncerMsg?.secondaryMessage?.message
+ },
+ { "Bouncer message update received: $int1, $str1, $int2, $str2" }
+ )
+ }
+
+ fun bindingBouncerMessageView() {
+ buffer.log(TAG, LogLevel.DEBUG, "Binding BouncerMessageView")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
index 2251a7b..0c2e731 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerLog.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -16,10 +16,7 @@
package com.android.systemui.log.dagger
-import java.lang.annotation.Documented
-import java.lang.annotation.Retention
-import java.lang.annotation.RetentionPolicy
import javax.inject.Qualifier
-/** Logger for the primary and alternative bouncers. */
-@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerLog
+/** A [com.android.systemui.log.LogBuffer] for bouncer and its child views. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BouncerLog()
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt
new file mode 100644
index 0000000..08df7db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/BouncerTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.log.dagger
+
+import java.lang.annotation.Documented
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import javax.inject.Qualifier
+
+/** Logger for the primary and alternative bouncers. */
+@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
new file mode 100644
index 0000000..62b80b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
@@ -0,0 +1,9 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for detailed carrier text logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CarrierTextManagerLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 408628f..0261ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -373,6 +373,16 @@
}
/**
+ * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+ */
+ @Provides
+ @SysUISingleton
+ @CarrierTextManagerLog
+ public static LogBuffer provideCarrierTextManagerLog(LogBufferFactory factory) {
+ return factory.create("CarrierTextManagerLog", 100);
+ }
+
+ /**
* Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
*/
@Provides
@@ -394,6 +404,17 @@
}
/**
+ * Provides a {@link LogBuffer} for use by classes in the
+ * {@link com.android.systemui.keyguard.bouncer} package.
+ */
+ @Provides
+ @SysUISingleton
+ @BouncerLog
+ public static LogBuffer provideBouncerLog(LogBufferFactory factory) {
+ return factory.create("BouncerLog", 100);
+ }
+
+ /**
* Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
*/
@Provides
@@ -416,9 +437,9 @@
/** Provides a logging buffer for the primary bouncer. */
@Provides
@SysUISingleton
- @BouncerLog
+ @BouncerTableLog
public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
- return factory.create("BouncerLog", 250);
+ return factory.create("BouncerTableLog", 250);
}
/** Provides a table logging buffer for the Monitor. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 8f0ac28..9eda7ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -1235,6 +1235,8 @@
if ((buttonId == R.id.actionPrev && semanticActions.getReservePrev())
|| (buttonId == R.id.actionNext && semanticActions.getReserveNext())) {
notVisibleValue = ConstraintSet.INVISIBLE;
+ mMediaViewHolder.getAction(buttonId).setFocusable(visible);
+ mMediaViewHolder.getAction(buttonId).setClickable(visible);
} else {
notVisibleValue = ConstraintSet.GONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 1627662..316c903e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -72,102 +72,67 @@
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
super.onCreateViewHolder(viewGroup, viewType);
- if (mController.isAdvancedLayoutSupported()) {
- switch (viewType) {
- case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
- return new MediaGroupDividerViewHolder(mHolderView);
- case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
- case MediaItem.MediaItemType.TYPE_DEVICE:
- default:
- return new MediaDeviceViewHolder(mHolderView);
- }
- } else {
- return new MediaDeviceViewHolder(mHolderView);
+ switch (viewType) {
+ case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+ return new MediaGroupDividerViewHolder(mHolderView);
+ case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
+ case MediaItem.MediaItemType.TYPE_DEVICE:
+ default:
+ return new MediaDeviceViewHolder(mHolderView);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
- if (mController.isAdvancedLayoutSupported()) {
- if (position >= mMediaItemList.size()) {
- if (DEBUG) {
- Log.d(TAG, "Incorrect position: " + position + " list size: "
- + mMediaItemList.size());
- }
- return;
+ if (position >= mMediaItemList.size()) {
+ if (DEBUG) {
+ Log.d(TAG, "Incorrect position: " + position + " list size: "
+ + mMediaItemList.size());
}
- MediaItem currentMediaItem = mMediaItemList.get(position);
- switch (currentMediaItem.getMediaItemType()) {
- case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
- ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
- break;
- case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
- ((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
- break;
- case MediaItem.MediaItemType.TYPE_DEVICE:
- ((MediaDeviceViewHolder) viewHolder).onBind(
- currentMediaItem.getMediaDevice().get(),
- position);
- break;
- default:
- Log.d(TAG, "Incorrect position: " + position);
- }
- } else {
- final int size = mController.getMediaDevices().size();
- if (position == size) {
+ return;
+ }
+ MediaItem currentMediaItem = mMediaItemList.get(position);
+ switch (currentMediaItem.getMediaItemType()) {
+ case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
+ ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
+ break;
+ case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW);
- } else if (position < size) {
+ break;
+ case MediaItem.MediaItemType.TYPE_DEVICE:
((MediaDeviceViewHolder) viewHolder).onBind(
- ((List<MediaDevice>) (mController.getMediaDevices())).get(position),
+ currentMediaItem.getMediaDevice().get(),
position);
- } else if (DEBUG) {
+ break;
+ default:
Log.d(TAG, "Incorrect position: " + position);
- }
}
}
@Override
public long getItemId(int position) {
- if (mController.isAdvancedLayoutSupported()) {
- if (position >= mMediaItemList.size()) {
- Log.d(TAG, "Incorrect position for item id: " + position);
- return position;
- }
- MediaItem currentMediaItem = mMediaItemList.get(position);
- return currentMediaItem.getMediaDevice().isPresent()
- ? currentMediaItem.getMediaDevice().get().getId().hashCode()
- : position;
- }
- final int size = mController.getMediaDevices().size();
- if (position == size) {
- return -1;
- } else if (position < size) {
- return ((List<MediaDevice>) (mController.getMediaDevices()))
- .get(position).getId().hashCode();
- } else if (DEBUG) {
+ if (position >= mMediaItemList.size()) {
Log.d(TAG, "Incorrect position for item id: " + position);
+ return position;
}
- return position;
+ MediaItem currentMediaItem = mMediaItemList.get(position);
+ return currentMediaItem.getMediaDevice().isPresent()
+ ? currentMediaItem.getMediaDevice().get().getId().hashCode()
+ : position;
}
@Override
public int getItemViewType(int position) {
- if (mController.isAdvancedLayoutSupported()
- && position >= mMediaItemList.size()) {
+ if (position >= mMediaItemList.size()) {
Log.d(TAG, "Incorrect position for item type: " + position);
return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;
}
- return mController.isAdvancedLayoutSupported()
- ? mMediaItemList.get(position).getMediaItemType()
- : super.getItemViewType(position);
+ return mMediaItemList.get(position).getMediaItemType();
}
@Override
public int getItemCount() {
- // Add extra one for "pair new"
- return mController.isAdvancedLayoutSupported()
- ? mMediaItemList.size()
- : mController.getMediaDevices().size() + 1;
+ return mMediaItemList.size();
}
class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
@@ -203,17 +168,14 @@
// Set different layout for each device
if (device.isMutingExpectedDevice()
&& !mController.isCurrentConnectedDeviceRemote()) {
- if (!mController.isAdvancedLayoutSupported()) {
- updateTitleIcon(R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
- }
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
mCurrentActivePosition = position;
updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device));
initMutingExpectedDevice();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
- && mController.isSubStatusSupported()
- && mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
+ && device.hasSubtext()) {
boolean isActiveWithOngoingSession =
(device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded(
mController.getSelectedMediaDevice(), device)));
@@ -284,10 +246,8 @@
// selected device in group
boolean isDeviceDeselectable = isDeviceIncluded(
mController.getDeselectableMediaDevice(), device);
- if (!mController.isAdvancedLayoutSupported()) {
- updateTitleIcon(R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
- }
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
updateGroupableCheckBox(true, isDeviceDeselectable, device);
updateEndClickArea(device, isDeviceDeselectable);
setUpContentDescriptionForView(mContainerLayout, false, device);
@@ -305,8 +265,7 @@
updateFullItemClickListener(v -> cancelMuteAwaitConnection());
setSingleLineLayout(getItemTitle(device));
} else if (mController.isCurrentConnectedDeviceRemote()
- && !mController.getSelectableMediaDevice().isEmpty()
- && mController.isAdvancedLayoutSupported()) {
+ && !mController.getSelectableMediaDevice().isEmpty()) {
//If device is connected and there's other selectable devices, layout as
// one of selected devices.
updateTitleIcon(R.drawable.media_output_icon_volume,
@@ -334,36 +293,27 @@
//groupable device
setUpDeviceIcon(device);
updateGroupableCheckBox(false, true, device);
- if (mController.isAdvancedLayoutSupported()) {
- updateEndClickArea(device, true);
- }
- updateFullItemClickListener(mController.isAdvancedLayoutSupported()
- ? v -> onItemClick(v, device)
- : v -> onGroupActionTriggered(true, device));
+ updateEndClickArea(device, true);
+ updateFullItemClickListener(v -> onItemClick(v, device));
setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
false /* showProgressBar */, true /* showCheckBox */,
true /* showEndTouchArea */);
} else {
setUpDeviceIcon(device);
setSingleLineLayout(getItemTitle(device));
- if (mController.isAdvancedLayoutSupported()
- && mController.isSubStatusSupported()) {
- Drawable deviceStatusIcon =
- device.hasOngoingSession() ? mContext.getDrawable(
- R.drawable.ic_sound_bars_anim)
- : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
- device,
- mContext);
- if (deviceStatusIcon != null) {
- updateDeviceStatusIcon(deviceStatusIcon);
- mStatusIcon.setVisibility(View.VISIBLE);
- }
- updateSingleLineLayoutContentAlpha(
- updateClickActionBasedOnSelectionBehavior(device)
- ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
- } else {
- updateFullItemClickListener(v -> onItemClick(v, device));
+ Drawable deviceStatusIcon =
+ device.hasOngoingSession() ? mContext.getDrawable(
+ R.drawable.ic_sound_bars_anim)
+ : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(
+ device,
+ mContext);
+ if (deviceStatusIcon != null) {
+ updateDeviceStatusIcon(deviceStatusIcon);
+ mStatusIcon.setVisibility(View.VISIBLE);
}
+ updateSingleLineLayoutContentAlpha(
+ updateClickActionBasedOnSelectionBehavior(device)
+ ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA);
}
}
}
@@ -400,10 +350,8 @@
}
public void updateEndClickAreaColor(int color) {
- if (mController.isAdvancedLayoutSupported()) {
- mEndTouchArea.setBackgroundTintList(
- ColorStateList.valueOf(color));
- }
+ mEndTouchArea.setBackgroundTintList(
+ ColorStateList.valueOf(color));
}
private boolean updateClickActionBasedOnSelectionBehavior(MediaDevice device) {
@@ -440,10 +388,8 @@
isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
mEndTouchArea.setImportantForAccessibility(
View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- if (mController.isAdvancedLayoutSupported()) {
- mEndTouchArea.setBackgroundTintList(
- ColorStateList.valueOf(mController.getColorItemBackground()));
- }
+ mEndTouchArea.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorItemBackground()));
setUpContentDescriptionForView(mEndTouchArea, true, device);
}
@@ -473,10 +419,8 @@
mTitleIcon.setImageDrawable(addDrawable);
mTitleIcon.setImageTintList(
ColorStateList.valueOf(mController.getColorItemContent()));
- if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.setBackgroundTintList(
- ColorStateList.valueOf(mController.getColorItemBackground()));
- }
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorItemBackground()));
mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 6ebda40..af06258 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -89,9 +89,8 @@
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
int viewType) {
mContext = viewGroup.getContext();
- mHolderView = LayoutInflater.from(mContext).inflate(
- mController.isAdvancedLayoutSupported() ? MediaItem.getMediaLayoutId(viewType)
- : R.layout.media_output_list_item, viewGroup, false);
+ mHolderView = LayoutInflater.from(mContext).inflate(MediaItem.getMediaLayoutId(viewType),
+ viewGroup, false);
return null;
}
@@ -173,15 +172,9 @@
mStatusIcon = view.requireViewById(R.id.media_output_item_status);
mCheckBox = view.requireViewById(R.id.check_box);
mEndTouchArea = view.requireViewById(R.id.end_action_area);
- if (mController.isAdvancedLayoutSupported()) {
- mEndClickIcon = view.requireViewById(R.id.media_output_item_end_click_icon);
- mVolumeValueText = view.requireViewById(R.id.volume_value);
- mIconAreaLayout = view.requireViewById(R.id.icon_area);
- } else {
- mVolumeValueText = null;
- mIconAreaLayout = null;
- mEndClickIcon = null;
- }
+ mEndClickIcon = view.requireViewById(R.id.media_output_item_end_click_icon);
+ mVolumeValueText = view.requireViewById(R.id.volume_value);
+ mIconAreaLayout = view.requireViewById(R.id.icon_area);
initAnimator();
}
@@ -197,9 +190,7 @@
mTitleText.setTextColor(mController.getColorItemContent());
mSubTitleText.setTextColor(mController.getColorItemContent());
mTwoLineTitleText.setTextColor(mController.getColorItemContent());
- if (mController.isAdvancedLayoutSupported()) {
- mVolumeValueText.setTextColor(mController.getColorItemContent());
- }
+ mVolumeValueText.setTextColor(mController.getColorItemContent());
mSeekBar.setProgressTintList(
ColorStateList.valueOf(mController.getColorSeekbarProgress()));
}
@@ -230,12 +221,10 @@
mItemLayout.setBackgroundTintList(
ColorStateList.valueOf(isActive ? mController.getColorConnectedItemBackground()
: mController.getColorItemBackground()));
- if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.setBackgroundTintList(
- ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress()
- : showProgressBar ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground()));
- }
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(showSeekBar ? mController.getColorSeekbarProgress()
+ : showProgressBar ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground()));
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
@@ -246,12 +235,10 @@
mTitleText.setVisibility(View.VISIBLE);
mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
- if (mController.isAdvancedLayoutSupported()) {
- ViewGroup.MarginLayoutParams params =
- (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
- params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
- : mController.getItemMarginEndDefault();
- }
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+ params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
+ : mController.getItemMarginEndDefault();
mTitleIcon.setBackgroundTintList(
ColorStateList.valueOf(mController.getColorItemContent()));
}
@@ -272,36 +259,28 @@
mSeekBar.setAlpha(1);
mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
final Drawable backgroundDrawable;
- if (mController.isAdvancedLayoutSupported() && mController.isSubStatusSupported()) {
- backgroundDrawable = mContext.getDrawable(
- showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active
- : R.drawable.media_output_item_background).mutate();
- mItemLayout.setBackgroundTintList(ColorStateList.valueOf(
- showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground()
- : mController.getColorItemBackground()
- ));
- mIconAreaLayout.setBackgroundTintList(
- ColorStateList.valueOf(showProgressBar || isFakeActive
- ? mController.getColorConnectedItemBackground()
- : showSeekBar ? mController.getColorSeekbarProgress()
- : mController.getColorItemBackground()));
- if (showSeekBar) {
- updateSeekbarProgressBackground();
- }
- //update end click area by isActive
- mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
- mEndClickIcon.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
- ViewGroup.MarginLayoutParams params =
- (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
- params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
- : mController.getItemMarginEndDefault();
- } else {
- backgroundDrawable = mContext.getDrawable(
- R.drawable.media_output_item_background)
- .mutate();
- mItemLayout.setBackgroundTintList(
- ColorStateList.valueOf(mController.getColorItemBackground()));
+ backgroundDrawable = mContext.getDrawable(
+ showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active
+ : R.drawable.media_output_item_background).mutate();
+ mItemLayout.setBackgroundTintList(ColorStateList.valueOf(
+ showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground()
+ : mController.getColorItemBackground()
+ ));
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(showProgressBar || isFakeActive
+ ? mController.getColorConnectedItemBackground()
+ : showSeekBar ? mController.getColorSeekbarProgress()
+ : mController.getColorItemBackground()));
+ if (showSeekBar) {
+ updateSeekbarProgressBackground();
}
+ //update end click area by isActive
+ mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ mEndClickIcon.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+ ViewGroup.MarginLayoutParams params =
+ (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+ params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
+ : mController.getItemMarginEndDefault();
mItemLayout.setBackground(backgroundDrawable);
mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
@@ -319,15 +298,11 @@
.findDrawableByLayerId(android.R.id.progress);
final GradientDrawable progressDrawable =
(GradientDrawable) clipDrawable.getDrawable();
- if (mController.isAdvancedLayoutSupported()) {
- progressDrawable.setCornerRadii(
- new float[]{0, 0, mController.getActiveRadius(),
- mController.getActiveRadius(),
- mController.getActiveRadius(),
- mController.getActiveRadius(), 0, 0});
- } else {
- progressDrawable.setCornerRadius(mController.getActiveRadius());
- }
+ progressDrawable.setCornerRadii(
+ new float[]{0, 0, mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(),
+ mController.getActiveRadius(), 0, 0});
}
void initSeekbar(MediaDevice device, boolean isCurrentSeekbarInvisible) {
@@ -340,30 +315,23 @@
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getVolume() != currentVolume) {
if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
- if (mController.isAdvancedLayoutSupported()) {
- updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
- : R.drawable.media_output_icon_volume,
- mController.getColorItemContent());
- } else {
- animateCornerAndVolume(mSeekBar.getProgress(),
- MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
- }
+ updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
+ : R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
} else {
if (!mVolumeAnimator.isStarted()) {
- if (mController.isAdvancedLayoutSupported()) {
- int percentage =
- (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
- / (double) mSeekBar.getMax());
- if (percentage == 0) {
- updateMutedVolumeIcon();
- } else {
- updateUnmutedVolumeIcon();
- }
+ int percentage =
+ (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) mSeekBar.getMax());
+ if (percentage == 0) {
+ updateMutedVolumeIcon();
+ } else {
+ updateUnmutedVolumeIcon();
}
mSeekBar.setVolume(currentVolume);
}
}
- } else if (mController.isAdvancedLayoutSupported() && currentVolume == 0) {
+ } else if (currentVolume == 0) {
mSeekBar.resetVolume();
updateMutedVolumeIcon();
}
@@ -378,17 +346,15 @@
}
int progressToVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
int deviceVolume = device.getCurrentVolume();
- if (mController.isAdvancedLayoutSupported()) {
- int percentage =
- (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE
- / (double) seekBar.getMax());
- mVolumeValueText.setText(mContext.getResources().getString(
- R.string.media_output_dialog_volume_percentage, percentage));
- mVolumeValueText.setVisibility(View.VISIBLE);
- }
+ int percentage =
+ (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) seekBar.getMax());
+ mVolumeValueText.setText(mContext.getResources().getString(
+ R.string.media_output_dialog_volume_percentage, percentage));
+ mVolumeValueText.setVisibility(View.VISIBLE);
if (progressToVolume != deviceVolume) {
mController.adjustVolume(device, progressToVolume);
- if (mController.isAdvancedLayoutSupported() && deviceVolume == 0) {
+ if (deviceVolume == 0) {
updateUnmutedVolumeIcon();
}
}
@@ -396,30 +362,26 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
- if (mController.isAdvancedLayoutSupported()) {
- mTitleIcon.setVisibility(View.INVISIBLE);
- mVolumeValueText.setVisibility(View.VISIBLE);
- }
+ mTitleIcon.setVisibility(View.INVISIBLE);
+ mVolumeValueText.setVisibility(View.VISIBLE);
mIsDragging = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
- if (mController.isAdvancedLayoutSupported()) {
- int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
- seekBar.getProgress());
- int percentage =
- (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
- / (double) seekBar.getMax());
- if (percentage == 0) {
- seekBar.setProgress(0);
- updateMutedVolumeIcon();
- } else {
- updateUnmutedVolumeIcon();
- }
- mTitleIcon.setVisibility(View.VISIBLE);
- mVolumeValueText.setVisibility(View.GONE);
+ int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+ seekBar.getProgress());
+ int percentage =
+ (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+ / (double) seekBar.getMax());
+ if (percentage == 0) {
+ seekBar.setProgress(0);
+ updateMutedVolumeIcon();
+ } else {
+ updateUnmutedVolumeIcon();
}
+ mTitleIcon.setVisibility(View.VISIBLE);
+ mVolumeValueText.setVisibility(View.GONE);
mController.logInteractionAdjustVolume(device);
mIsDragging = false;
}
@@ -444,10 +406,8 @@
void updateTitleIcon(@DrawableRes int id, int color) {
mTitleIcon.setImageDrawable(mContext.getDrawable(id));
mTitleIcon.setImageTintList(ColorStateList.valueOf(color));
- if (mController.isAdvancedLayoutSupported()) {
- mIconAreaLayout.setBackgroundTintList(
- ColorStateList.valueOf(mController.getColorSeekbarProgress()));
- }
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorSeekbarProgress()));
}
void updateIconAreaClickListener(View.OnClickListener listener) {
@@ -475,24 +435,18 @@
(ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
.findDrawableByLayerId(android.R.id.progress);
final GradientDrawable targetBackgroundDrawable =
- (GradientDrawable) (mController.isAdvancedLayoutSupported()
- ? mIconAreaLayout.getBackground()
- : clipDrawable.getDrawable());
+ (GradientDrawable) (mIconAreaLayout.getBackground());
mCornerAnimator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
layoutBackgroundDrawable.setCornerRadius(value);
- if (mController.isAdvancedLayoutSupported()) {
- if (toProgress == 0) {
- targetBackgroundDrawable.setCornerRadius(value);
- } else {
- targetBackgroundDrawable.setCornerRadii(new float[]{
- value,
- value,
- 0, 0, 0, 0, value, value
- });
- }
- } else {
+ if (toProgress == 0) {
targetBackgroundDrawable.setCornerRadius(value);
+ } else {
+ targetBackgroundDrawable.setCornerRadii(new float[]{
+ value,
+ value,
+ 0, 0, 0, 0, value, value
+ });
}
});
mVolumeAnimator.setIntValues(fromProgress, toProgress);
@@ -539,9 +493,7 @@
protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
- if (mController.isAdvancedLayoutSupported()) {
- updateIconAreaClickListener(null);
- }
+ updateIconAreaClickListener(null);
}
private void enableSeekBar(MediaDevice device) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 08b799a..0a5b4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -270,10 +270,8 @@
dismiss();
});
mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
- if (mMediaOutputController.isAdvancedLayoutSupported()) {
- mMediaMetadataSectionLayout.setOnClickListener(
- mMediaOutputController::tryToLaunchMediaApplication);
- }
+ mMediaMetadataSectionLayout.setOnClickListener(
+ mMediaOutputController::tryToLaunchMediaApplication);
}
@Override
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 2713642..cc75478 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -83,7 +83,6 @@
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.plugins.ActivityStarter;
@@ -132,8 +131,6 @@
private final Object mMediaDevicesLock = new Object();
@VisibleForTesting
final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
- @VisibleForTesting
- final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
@@ -229,7 +226,6 @@
void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaDevices.clear();
mMediaItemList.clear();
}
mNearbyDeviceInfoMap.clear();
@@ -277,7 +273,6 @@
mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaDevices.clear();
mMediaItemList.clear();
}
if (mNearbyMediaDevicesManager != null) {
@@ -308,10 +303,9 @@
@Override
public void onDeviceListUpdate(List<MediaDevice> devices) {
- boolean isListEmpty =
- isAdvancedLayoutSupported() ? mMediaItemList.isEmpty() : mMediaDevices.isEmpty();
+ boolean isListEmpty = mMediaItemList.isEmpty();
if (isListEmpty || !mIsRefreshing) {
- buildMediaDevices(devices);
+ buildMediaItems(devices);
mCallback.onDeviceListChanged();
} else {
synchronized (mMediaDevicesLock) {
@@ -326,11 +320,7 @@
public void onSelectedDeviceStateChanged(MediaDevice device,
@LocalMediaManager.MediaDeviceState int state) {
mCallback.onRouteChanged();
- if (isAdvancedLayoutSupported()) {
- mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
- } else {
- mMetricLogger.logOutputSuccess(device.toString(), new ArrayList<>(mMediaDevices));
- }
+ mMetricLogger.logOutputItemSuccess(device.toString(), new ArrayList<>(mMediaItemList));
}
@Override
@@ -341,11 +331,7 @@
@Override
public void onRequestFailed(int reason) {
mCallback.onRouteChanged();
- if (isAdvancedLayoutSupported()) {
- mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
- } else {
- mMetricLogger.logOutputFailure(new ArrayList<>(mMediaDevices), reason);
- }
+ mMetricLogger.logOutputItemFailure(new ArrayList<>(mMediaItemList), reason);
}
/**
@@ -364,7 +350,6 @@
}
try {
synchronized (mMediaDevicesLock) {
- mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice);
mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
@@ -569,7 +554,7 @@
void refreshDataSetIfNeeded() {
if (mNeedRefresh) {
- buildMediaDevices(mCachedMediaDevices);
+ buildMediaItems(mCachedMediaDevices);
mCallback.onDeviceListChanged();
mNeedRefresh = false;
}
@@ -619,75 +604,9 @@
return mItemMarginEndSelectable;
}
- private void buildMediaDevices(List<MediaDevice> devices) {
- if (isAdvancedLayoutSupported()) {
- buildMediaItems(devices);
- } else {
- buildDefaultMediaDevices(devices);
- }
- }
-
- private void buildDefaultMediaDevices(List<MediaDevice> devices) {
- synchronized (mMediaDevicesLock) {
- attachRangeInfo(devices);
- Collections.sort(devices, Comparator.naturalOrder());
- // For the first time building list, to make sure the top device is the connected
- // device.
- if (mMediaDevices.isEmpty()) {
- boolean needToHandleMutingExpectedDevice =
- hasMutingExpectedDevice() && !isCurrentConnectedDeviceRemote();
- final MediaDevice connectedMediaDevice =
- needToHandleMutingExpectedDevice ? null
- : getCurrentConnectedMediaDevice();
- if (connectedMediaDevice == null) {
- if (DEBUG) {
- Log.d(TAG, "No connected media device or muting expected device exist.");
- }
- if (needToHandleMutingExpectedDevice) {
- for (MediaDevice device : devices) {
- if (device.isMutingExpectedDevice()) {
- mMediaDevices.add(0, device);
- } else {
- mMediaDevices.add(device);
- }
- }
- } else {
- mMediaDevices.addAll(devices);
- }
- return;
- }
- for (MediaDevice device : devices) {
- if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) {
- mMediaDevices.add(0, device);
- } else {
- mMediaDevices.add(device);
- }
- }
- return;
- }
- // To keep the same list order
- final List<MediaDevice> targetMediaDevices = new ArrayList<>();
- for (MediaDevice originalDevice : mMediaDevices) {
- for (MediaDevice newDevice : devices) {
- if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
- targetMediaDevices.add(newDevice);
- break;
- }
- }
- }
- if (targetMediaDevices.size() != devices.size()) {
- devices.removeAll(targetMediaDevices);
- targetMediaDevices.addAll(devices);
- }
- mMediaDevices.clear();
- mMediaDevices.addAll(targetMediaDevices);
- }
- }
-
private void buildMediaItems(List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
- if (!isRouteProcessSupported() || (isRouteProcessSupported()
- && !mLocalMediaManager.isPreferenceRouteListingExist())) {
+ if (!mLocalMediaManager.isPreferenceRouteListingExist()) {
attachRangeInfo(devices);
Collections.sort(devices, Comparator.naturalOrder());
}
@@ -811,18 +730,6 @@
&& (currentConnectedMediaDevice.isHostForOngoingSession());
}
- public boolean isAdvancedLayoutSupported() {
- return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT);
- }
-
- public boolean isRouteProcessSupported() {
- return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING);
- }
-
- public boolean isSubStatusSupported() {
- return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_DEVICE_STATUS);
- }
-
List<MediaDevice> getGroupMediaDevices() {
final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -867,10 +774,6 @@
});
}
- Collection<MediaDevice> getMediaDevices() {
- return mMediaDevices;
- }
-
public List<MediaItem> getMediaItemList() {
return mMediaItemList;
}
@@ -957,19 +860,11 @@
boolean isAnyDeviceTransferring() {
synchronized (mMediaDevicesLock) {
- if (isAdvancedLayoutSupported()) {
- for (MediaItem mediaItem : mMediaItemList) {
- if (mediaItem.getMediaDevice().isPresent()
- && mediaItem.getMediaDevice().get().getState()
- == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
- return true;
- }
- }
- } else {
- for (MediaDevice device : mMediaDevices) {
- if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
- return true;
- }
+ for (MediaItem mediaItem : mMediaItemList) {
+ if (mediaItem.getMediaDevice().isPresent()
+ && mediaItem.getMediaDevice().get().getState()
+ == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) {
+ return true;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 8af488e..417d4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -321,7 +321,8 @@
protected void setBackgroundTintColor(int color) {
if (color != mCurrentBackgroundTint) {
mCurrentBackgroundTint = color;
- if (color == mNormalColor) {
+ // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe
+ if (false && color == mNormalColor) {
// We don't need to tint a normal notification
color = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 6bbeebf..0989df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.notification.row;
+import static android.graphics.PorterDuff.Mode.SRC_ATOP;
+
import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
@@ -157,10 +161,20 @@
*/
public void updateColors() {
Resources.Theme theme = mContext.getTheme();
- int textColor = getResources().getColor(R.color.notif_pill_text, theme);
- mClearAllButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+ final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme);
+ final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
+ final @ColorInt int buttonBgColor =
+ Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface);
+ final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP);
+ if (buttonBgColor != 0) {
+ clearAllBg.setColorFilter(bgColorFilter);
+ manageBg.setColorFilter(bgColorFilter);
+ }
+ mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(textColor);
- mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+ mManageButton.setBackground(manageBg);
mManageButton.setTextColor(textColor);
final @ColorInt int labelTextColor =
Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index eb20bba..991ff56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -21,6 +21,7 @@
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
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.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
@@ -62,6 +63,7 @@
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class MobileRepositorySwitcher
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index b129617..e96288a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -19,6 +19,7 @@
import android.os.Bundle
import androidx.annotation.VisibleForTesting
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.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
@@ -54,6 +55,7 @@
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class WifiRepositorySwitcher
@Inject
constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 4fbbc89..ab6409b 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -21,6 +21,8 @@
import android.content.Context
import android.graphics.Rect
import android.os.PowerManager
+import android.os.Process
+import android.os.VibrationAttributes
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
@@ -226,7 +228,15 @@
maybeGetAccessibilityFocus(newInfo, currentView)
// ---- Haptics ----
- newInfo.vibrationEffect?.let { vibratorHelper.vibrate(it) }
+ newInfo.vibrationEffect?.let {
+ vibratorHelper.vibrate(
+ Process.myUid(),
+ context.getApplicationContext().getPackageName(),
+ it,
+ newInfo.windowTitle,
+ VIBRATION_ATTRIBUTES,
+ )
+ }
}
private fun maybeGetAccessibilityFocus(info: ChipbarInfo?, view: ViewGroup) {
@@ -352,6 +362,11 @@
val loadingView: View,
val animator: ObjectAnimator,
)
+
+ companion object {
+ val VIBRATION_ATTRIBUTES: VibrationAttributes =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
+ }
}
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 5557efa..48a8d1b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -21,6 +21,8 @@
import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertTrue;
@@ -30,13 +32,18 @@
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
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.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.telephony.ServiceState;
@@ -47,8 +54,10 @@
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import com.android.keyguard.logging.CarrierTextManagerLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.LogBufferHelperKt;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
@@ -110,6 +119,10 @@
private CarrierTextManager mCarrierTextManager;
+ private CarrierTextManagerLogger mLogger =
+ new CarrierTextManagerLogger(
+ LogBufferHelperKt.logcatLogBuffer("CarrierTextManagerLog"));
+
private Void checkMainThread(InvocationOnMock inv) {
assertThat(mMainExecutor.isExecuting()).isTrue();
assertThat(mBgExecutor.isExecuting()).isFalse();
@@ -144,7 +157,7 @@
mCarrierTextManager = new CarrierTextManager.Builder(
mContext, mContext.getResources(), mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
- mBgExecutor, mKeyguardUpdateMonitor)
+ mBgExecutor, mKeyguardUpdateMonitor, mLogger)
.setShowAirplaneMode(true)
.setShowMissingSim(true)
.build();
@@ -183,6 +196,47 @@
assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
}
+ /** regression test for b/281706473, caused by sending NULL plmn / spn to the logger */
+ @Test
+ public void testAirplaneMode_noSim_nullPlmn_nullSpn_doesNotCrash() {
+ // GIVEN - sticy broadcast that returns a null PLMN and null SPN
+ Intent stickyIntent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, true);
+ stickyIntent.removeExtra(TelephonyManager.EXTRA_PLMN);
+ stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, true);
+ stickyIntent.removeExtra(TelephonyManager.EXTRA_SPN);
+
+ mCarrierTextManager = new CarrierTextManager.Builder(
+ getContextSpyForStickyBroadcast(stickyIntent),
+ mContext.getResources(),
+ mWifiRepository,
+ mTelephonyManager,
+ mTelephonyListenerManager,
+ mWakefulnessLifecycle,
+ mMainExecutor,
+ mBgExecutor,
+ mKeyguardUpdateMonitor,
+ mLogger
+ )
+ .setShowAirplaneMode(true)
+ .setShowMissingSim(true)
+ .build();
+
+ // GIVEN - airplane mode is off (causing CTM to fetch the sticky broadcast)
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getSimState(0))
+ .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN CTM fetches the broadcast and attempts to log the result, no crash results
+ mCarrierTextManager.updateCarrierText();
+
+ // No assert, this test should not crash
+ }
+
@Test
public void testCardIOError() {
reset(mCarrierTextCallback);
@@ -506,4 +560,10 @@
assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
captor.getValue().carrierText);
}
+
+ private Context getContextSpyForStickyBroadcast(Intent returnVal) {
+ Context contextSpy = spy(mContext);
+ doReturn(returnVal).when(contextSpy).registerReceiver(eq(null), any(IntentFilter.class));
+ return contextSpy;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 1ba9931..ae3a320 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -40,6 +40,8 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -77,6 +79,7 @@
@Mock
private EmergencyButtonController mEmergencyButtonController;
+ private FakeFeatureFlags mFeatureFlags;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@Before
@@ -90,10 +93,18 @@
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
.thenReturn(mKeyguardMessageArea);
when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
- mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
+ mFeatureFlags = new FakeFeatureFlags();
+ mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false);
+ mKeyguardAbsKeyInputViewController = createTestObject();
+ mKeyguardAbsKeyInputViewController.init();
+ reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
+ }
+
+ private KeyguardAbsKeyInputViewController createTestObject() {
+ return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
- mEmergencyButtonController) {
+ mEmergencyButtonController, mFeatureFlags) {
@Override
void resetState() {
}
@@ -108,8 +119,16 @@
return 0;
}
};
- mKeyguardAbsKeyInputViewController.init();
- reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
+ }
+
+ @Test
+ public void withFeatureFlagOn_oldMessage_isHidden() {
+ mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
+ KeyguardAbsKeyInputViewController underTest = createTestObject();
+
+ underTest.init();
+
+ verify(mKeyguardMessageAreaController).disable();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index d8e2a38..fb73845 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -134,7 +134,6 @@
private KeyguardClockSwitchController mController;
private View mSliceView;
- private LinearLayout mStatusArea;
private FakeExecutor mExecutor;
@Before
@@ -196,8 +195,8 @@
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
- mStatusArea = new LinearLayout(getContext());
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(
+ new LinearLayout(getContext()));
}
@Test
@@ -402,15 +401,6 @@
assertNull(mController.getClock());
}
- @Test
- public void testSetAlpha_setClockAlphaForCLockFace() {
- mController.onViewAttached();
- mController.setAlpha(0.5f);
- verify(mLargeClockView).setAlpha(0.5f);
- verify(mSmallClockView).setAlpha(0.5f);
- assertEquals(0.5f, mStatusArea.getAlpha(), 0.0f);
- }
-
private void verifyAttachment(VerificationMode times) {
verify(mClockRegistry, times).registerClockChangeListener(
any(ClockRegistry.ClockChangeListener.class));
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index 082c8cc..1a9260c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
@@ -76,6 +78,8 @@
Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
.thenReturn(passwordEntry)
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
+ val fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -90,7 +94,8 @@
mainExecutor,
mContext.resources,
falsingCollector,
- keyguardViewController)
+ keyguardViewController,
+ fakeFeatureFlags)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index a8d5569..71a57c7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -26,6 +26,8 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
@@ -72,6 +74,7 @@
@Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+ private lateinit var fakeFeatureFlags: FakeFeatureFlags
@Before
fun setup() {
@@ -86,6 +89,8 @@
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
.thenReturn(mKeyguardMessageAreaController)
`when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+ fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
mKeyguardPatternViewController =
KeyguardPatternViewController(
mKeyguardPatternView,
@@ -97,7 +102,17 @@
mFalsingCollector,
mEmergencyButtonController,
mKeyguardMessageAreaControllerFactory,
- mPostureController)
+ mPostureController,
+ fakeFeatureFlags)
+ }
+
+ @Test
+ fun withFeatureFlagOn_oldMessage_isHidden() {
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+
+ mKeyguardPatternViewController.init()
+
+ verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 0881e61..cf86c21 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -36,6 +36,8 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.SingleTapClassifier;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -98,10 +100,13 @@
.thenReturn(mDeleteButton);
when(mPinBasedInputView.findViewById(R.id.key_enter))
.thenReturn(mOkButton);
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
+
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
- mEmergencyButtonController, mFalsingCollector) {
+ mEmergencyButtonController, mFalsingCollector, featureFlags) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 65ddb53..1559c64 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -63,7 +63,9 @@
import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -195,11 +197,15 @@
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+ featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
+
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
- null, mock(Resources.class), null, mKeyguardViewController);
+ null, mock(Resources.class), null, mKeyguardViewController,
+ featureFlags);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index eb86c05..a3acc78 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -71,6 +73,9 @@
simPinView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
+ val fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+
underTest =
KeyguardSimPinViewController(
simPinView,
@@ -83,7 +88,8 @@
liftToActivateListener,
telephonyManager,
falsingCollector,
- emergencyButtonController
+ emergencyButtonController,
+ fakeFeatureFlags,
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 2dcca55..efcf4dd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -70,6 +72,9 @@
simPukView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
as KeyguardSimPukView
+ val fakeFeatureFlags = FakeFeatureFlags()
+ fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+
underTest =
KeyguardSimPukViewController(
simPukView,
@@ -82,7 +87,8 @@
liftToActivateListener,
telephonyManager,
falsingCollector,
- emergencyButtonController
+ emergencyButtonController,
+ fakeFeatureFlags,
)
underTest.init()
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index a8c281c..f8262d4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -24,46 +24,33 @@
get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
private val statusViewContainer: ViewGroup
get() = keyguardStatusView.findViewById(R.id.status_view_container)
- private val clockView: ViewGroup
- get() = keyguardStatusView.findViewById(R.id.keyguard_clock_container)
private val childrenExcludingMedia
get() = statusViewContainer.children.filter { it != mediaView }
@Before
fun setUp() {
- keyguardStatusView = LayoutInflater.from(context)
- .inflate(R.layout.keyguard_status_view, /* root= */ null) as KeyguardStatusView
+ keyguardStatusView =
+ LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, /* root= */ null)
+ as KeyguardStatusView
}
@Test
fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
val translationY = 1234f
- keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */true)
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */ true)
assertThat(mediaView.translationY).isEqualTo(0)
- childrenExcludingMedia.forEach {
- assertThat(it.translationY).isEqualTo(translationY)
- }
+ childrenExcludingMedia.forEach { assertThat(it.translationY).isEqualTo(translationY) }
}
@Test
fun setChildrenTranslationYIncludeMediaView() {
val translationY = 1234f
- keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */false)
+ keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */ false)
- statusViewContainer.children.forEach {
- assertThat(it.translationY).isEqualTo(translationY)
- }
- }
-
- @Test
- fun setAlphaExcludeClock() {
- keyguardStatusView.setAlpha(0.5f, /* excludeClock= */true)
- assertThat(statusViewContainer.alpha).isNotEqualTo(0.5f)
- assertThat(mediaView.alpha).isEqualTo(0.5f)
- assertThat(clockView.alpha).isNotEqualTo(0.5f)
+ statusViewContainer.children.forEach { assertThat(it.translationY).isEqualTo(translationY) }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
new file mode 100644
index 0000000..9e79849
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/factory/BouncerMessageFactoryTest.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.data.factory
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
+import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.StringSubject
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageFactoryTest : SysuiTestCase() {
+ private lateinit var underTest: BouncerMessageFactory
+
+ @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+
+ @Mock private lateinit var securityModel: KeyguardSecurityModel
+
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ underTest = BouncerMessageFactory(updateMonitor, securityModel)
+ }
+
+ @Test
+ fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+ testScope.runTest {
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
+ .isEqualTo("Enter PIN")
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
+ .isEqualTo("Unlock with PIN or fingerprint")
+
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
+ .isEqualTo("Enter password")
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
+ .isEqualTo("Unlock with password or fingerprint")
+
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
+ .isEqualTo("Draw pattern")
+ primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
+ .isEqualTo("Unlock with pattern or fingerprint")
+ }
+
+ @Test
+ fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
+ testScope.runTest {
+ primaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = PIN,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Wrong PIN. Try again.")
+ secondaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = PIN,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Or unlock with fingerprint")
+
+ primaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = Password,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Wrong password. Try again.")
+ secondaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = Password,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Or unlock with fingerprint")
+
+ primaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = Pattern,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Wrong pattern. Try again.")
+ secondaryMessage(
+ PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
+ mode = Pattern,
+ fpAllowedInBouncer = true
+ )
+ .isEqualTo("Or unlock with fingerprint")
+ }
+
+ private fun primaryMessage(
+ reason: Int,
+ mode: KeyguardSecurityModel.SecurityMode,
+ fpAllowedInBouncer: Boolean
+ ): StringSubject {
+ return assertThat(
+ context.resources.getString(
+ bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
+ )
+ )!!
+ }
+
+ private fun secondaryMessage(
+ reason: Int,
+ mode: KeyguardSecurityModel.SecurityMode,
+ fpAllowedInBouncer: Boolean
+ ): StringSubject {
+ return assertThat(
+ context.resources.getString(
+ bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
+ .secondaryMessage!!
+ .messageResId!!
+ )
+ )!!
+ }
+
+ private fun bouncerMessageModel(
+ mode: KeyguardSecurityModel.SecurityMode,
+ fpAllowedInBouncer: Boolean,
+ reason: Int
+ ): BouncerMessageModel? {
+ whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
+ whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
+
+ return underTest.createFromPromptReason(reason, 0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
new file mode 100644
index 0000000..1277fc0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/data/repository/BouncerMessageRepositoryTest.kt
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.data.repository
+
+import android.content.pm.UserInfo
+import android.hardware.biometrics.BiometricSourceType
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.R
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_prompt_after_dpm_lock
+import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
+import com.android.systemui.R.string.kg_prompt_auth_timeout
+import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
+import com.android.systemui.R.string.kg_prompt_reason_restart_pin
+import com.android.systemui.R.string.kg_prompt_unattended_update
+import com.android.systemui.R.string.kg_trust_agent_disabled
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.bouncer.shared.model.Message
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var securityModel: KeyguardSecurityModel
+ @Captor
+ private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+ private lateinit var underTest: BouncerMessageRepository
+ private lateinit var trustRepository: FakeTrustRepository
+ private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
+ private lateinit var testScope: TestScope
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ trustRepository = FakeTrustRepository()
+ biometricSettingsRepository = FakeBiometricSettingsRepository()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(listOf(PRIMARY_USER))
+ fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
+ testScope = TestScope()
+
+ whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+ whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+ underTest =
+ BouncerMessageRepositoryImpl(
+ trustRepository = trustRepository,
+ biometricSettingsRepository = biometricSettingsRepository,
+ updateMonitor = updateMonitor,
+ bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
+ userRepository = userRepository,
+ fingerprintAuthRepository = fingerprintRepository
+ )
+ }
+
+ @Test
+ fun setCustomMessage_propagatesState() =
+ testScope.runTest {
+ underTest.setCustomMessage(message("not empty"))
+
+ val customMessage = collectLastValue(underTest.customMessage)
+
+ assertThat(customMessage()).isEqualTo(message("not empty"))
+ }
+
+ @Test
+ fun setFaceMessage_propagatesState() =
+ testScope.runTest {
+ underTest.setFaceAcquisitionMessage(message("not empty"))
+
+ val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
+
+ assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
+ }
+
+ @Test
+ fun setFpMessage_propagatesState() =
+ testScope.runTest {
+ underTest.setFingerprintAcquisitionMessage(message("not empty"))
+
+ val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
+
+ assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
+ }
+
+ @Test
+ fun setPrimaryAuthMessage_propagatesState() =
+ testScope.runTest {
+ underTest.setPrimaryAuthMessage(message("not empty"))
+
+ val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
+
+ assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
+ }
+
+ @Test
+ fun biometricAuthMessage_propagatesBiometricAuthMessages() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
+ runCurrent()
+
+ verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+ updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
+
+ assertThat(biometricAuthMessage())
+ .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
+
+ updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
+
+ assertThat(biometricAuthMessage())
+ .isEqualTo(
+ message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
+ )
+
+ updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
+
+ assertThat(biometricAuthMessage()).isNull()
+ }
+
+ @Test
+ fun onFaceLockout_propagatesState() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+ runCurrent()
+ verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
+
+ whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
+ updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+
+ assertThat(lockoutMessage())
+ .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
+
+ whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
+ updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
+ assertThat(lockoutMessage()).isNull()
+ }
+
+ @Test
+ fun onFingerprintLockout_propagatesState() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
+ runCurrent()
+
+ fingerprintRepository.setLockedOut(true)
+
+ assertThat(lockedOutMessage())
+ .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
+
+ fingerprintRepository.setLockedOut(false)
+ assertThat(lockedOutMessage()).isNull()
+ }
+
+ @Test
+ fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ trustRepository.setCurrentUserTrustManaged(false)
+ biometricSettingsRepository.setFaceEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
+
+ verifyMessagesForAuthFlag(
+ STRONG_AUTH_NOT_REQUIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
+ SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+ STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
+ STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+ )
+ }
+
+ @Test
+ fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ biometricSettingsRepository.setFaceEnrolled(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
+
+ trustRepository.setCurrentUserTrustManaged(true)
+
+ verifyMessagesForAuthFlag(
+ STRONG_AUTH_NOT_REQUIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+ STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+ STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+ SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
+ Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
+ Pair(keyguard_enter_pin, kg_trust_agent_disabled),
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+ STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+ )
+ }
+
+ @Test
+ fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ trustRepository.setCurrentUserTrustManaged(false)
+ biometricSettingsRepository.setFingerprintEnrolled(false)
+
+ biometricSettingsRepository.setIsFaceAuthEnabled(true)
+ biometricSettingsRepository.setFaceEnrolled(true)
+
+ verifyMessagesForAuthFlag(
+ STRONG_AUTH_NOT_REQUIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+ STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+ STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+ STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+ )
+ }
+
+ @Test
+ fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ trustRepository.setCurrentUserTrustManaged(false)
+ biometricSettingsRepository.setIsFaceAuthEnabled(false)
+ biometricSettingsRepository.setFaceEnrolled(false)
+
+ biometricSettingsRepository.setFingerprintEnrolled(true)
+ biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
+
+ verifyMessagesForAuthFlag(
+ STRONG_AUTH_NOT_REQUIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
+ SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
+ SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
+ STRONG_AUTH_REQUIRED_AFTER_BOOT to
+ Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
+ STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
+ STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
+ Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
+ Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
+ STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
+ Pair(keyguard_enter_pin, kg_prompt_unattended_update),
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
+ Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
+ )
+ }
+
+ private fun TestScope.verifyMessagesForAuthFlag(
+ vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
+ ) {
+ val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
+
+ authFlagToExpectedMessages.forEach { (flag, messagePair) ->
+ biometricSettingsRepository.setAuthenticationFlags(
+ AuthenticationFlags(PRIMARY_USER_ID, flag)
+ )
+
+ assertThat(authFlagsMessage())
+ .isEqualTo(messagePair?.let { message(it.first, it.second) })
+ }
+ }
+
+ private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
+ return BouncerMessageModel(
+ message = Message(messageResId = primaryResId),
+ secondaryMessage = Message(messageResId = secondaryResId)
+ )
+ }
+ private fun message(value: String): BouncerMessageModel {
+ return BouncerMessageModel(message = Message(message = value))
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
new file mode 100644
index 0000000..b0af310
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.bouncer.domain.interactor
+
+import android.content.pm.UserInfo
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R.string.keyguard_enter_pin
+import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.FlowValue
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import com.android.systemui.keyguard.bouncer.shared.model.Message
+import com.android.systemui.keyguard.data.repository.FakeBouncerMessageRepository
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class BouncerMessageInteractorTest : SysuiTestCase() {
+
+ @Mock private lateinit var securityModel: KeyguardSecurityModel
+ @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
+ private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
+ private lateinit var underTest: BouncerMessageInteractor
+ private lateinit var repository: FakeBouncerMessageRepository
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var testScope: TestScope
+ private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeBouncerMessageRepository()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(listOf(PRIMARY_USER))
+ testScope = TestScope()
+ countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
+
+ allowTestableLooperAsMainThread()
+ whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
+ whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
+ }
+
+ suspend fun TestScope.init() {
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ underTest =
+ BouncerMessageInteractor(
+ repository = repository,
+ factory = BouncerMessageFactory(updateMonitor, securityModel),
+ userRepository = userRepository,
+ countDownTimerUtil = countDownTimerUtil,
+ featureFlags = featureFlags
+ )
+ bouncerMessage = collectLastValue(underTest.bouncerMessage)
+ }
+
+ @Test
+ fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
+ testScope.runTest {
+ init()
+ underTest.onPrimaryAuthIncorrectAttempt()
+
+ assertThat(repository.primaryAuthMessage).isNotNull()
+ assertThat(
+ context.resources.getString(
+ repository.primaryAuthMessage.value!!.message!!.messageResId!!
+ )
+ )
+ .isEqualTo("Wrong PIN. Try again.")
+ }
+
+ @Test
+ fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
+ testScope.runTest {
+ init()
+ repository.setCustomMessage(message("not empty"))
+ repository.setFaceAcquisitionMessage(message("not empty"))
+ repository.setFingerprintAcquisitionMessage(message("not empty"))
+ repository.setPrimaryAuthMessage(message("not empty"))
+
+ underTest.onPrimaryBouncerUserInput()
+
+ assertThat(repository.customMessage.value).isNull()
+ assertThat(repository.faceAcquisitionMessage.value).isNull()
+ assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+ assertThat(repository.primaryAuthMessage.value).isNull()
+ }
+
+ @Test
+ fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
+ testScope.runTest {
+ init()
+ repository.setCustomMessage(message("not empty"))
+ repository.setFaceAcquisitionMessage(message("not empty"))
+ repository.setFingerprintAcquisitionMessage(message("not empty"))
+ repository.setPrimaryAuthMessage(message("not empty"))
+
+ underTest.onBouncerBeingHidden()
+
+ assertThat(repository.customMessage.value).isNull()
+ assertThat(repository.faceAcquisitionMessage.value).isNull()
+ assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+ assertThat(repository.primaryAuthMessage.value).isNull()
+ }
+
+ @Test
+ fun setCustomMessage_setsRepositoryValue() =
+ testScope.runTest {
+ init()
+
+ underTest.setCustomMessage("not empty")
+
+ assertThat(repository.customMessage.value)
+ .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+ underTest.setCustomMessage(null)
+ assertThat(repository.customMessage.value).isNull()
+ }
+
+ @Test
+ fun setFaceMessage_setsRepositoryValue() =
+ testScope.runTest {
+ init()
+
+ underTest.setFaceAcquisitionMessage("not empty")
+
+ assertThat(repository.faceAcquisitionMessage.value)
+ .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+ underTest.setFaceAcquisitionMessage(null)
+ assertThat(repository.faceAcquisitionMessage.value).isNull()
+ }
+
+ @Test
+ fun setFingerprintMessage_setsRepositoryValue() =
+ testScope.runTest {
+ init()
+
+ underTest.setFingerprintAcquisitionMessage("not empty")
+
+ assertThat(repository.fingerprintAcquisitionMessage.value)
+ .isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
+
+ underTest.setFingerprintAcquisitionMessage(null)
+ assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+ }
+
+ @Test
+ fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
+ testScope.runTest {
+ init()
+
+ underTest.onPrimaryAuthLockedOut(3)
+
+ verify(countDownTimerUtil)
+ .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+ countDownTimerCallback.value.onTick(2000L)
+
+ val primaryMessage = repository.primaryAuthMessage.value!!.message!!
+ assertThat(primaryMessage.messageResId!!)
+ .isEqualTo(kg_too_many_failed_attempts_countdown)
+ assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
+ }
+
+ @Test
+ fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
+ testScope.runTest {
+ init()
+ repository.setCustomMessage(message("not empty"))
+ repository.setFaceAcquisitionMessage(message("not empty"))
+ repository.setFingerprintAcquisitionMessage(message("not empty"))
+ repository.setPrimaryAuthMessage(message("not empty"))
+
+ underTest.onPrimaryAuthLockedOut(3)
+
+ verify(countDownTimerUtil)
+ .startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
+
+ countDownTimerCallback.value.onFinish()
+
+ assertThat(repository.customMessage.value).isNull()
+ assertThat(repository.faceAcquisitionMessage.value).isNull()
+ assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
+ assertThat(repository.primaryAuthMessage.value).isNull()
+ }
+
+ @Test
+ fun bouncerMessage_hasPriorityOrderOfMessages() =
+ testScope.runTest {
+ init()
+ repository.setBiometricAuthMessage(message("biometric message"))
+ repository.setFaceAcquisitionMessage(message("face acquisition message"))
+ repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
+ repository.setPrimaryAuthMessage(message("primary auth message"))
+ repository.setAuthFlagsMessage(message("auth flags message"))
+ repository.setBiometricLockedOutMessage(message("biometrics locked out"))
+ repository.setCustomMessage(message("custom message"))
+
+ assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
+
+ repository.setPrimaryAuthMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
+
+ repository.setBiometricAuthMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
+
+ repository.setFingerprintAcquisitionMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
+
+ repository.setFaceAcquisitionMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("custom message"))
+
+ repository.setCustomMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
+
+ repository.setAuthFlagsMessage(null)
+
+ assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
+
+ repository.setBiometricLockedOutMessage(null)
+
+ // sets the default message if everything else is null
+ assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
+ }
+
+ private fun message(value: String): BouncerMessageModel {
+ return BouncerMessageModel(message = Message(message = value))
+ }
+
+ companion object {
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PRIMARY
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 1bab817..b925aeb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -310,19 +310,38 @@
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
-
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(0)
broadcastDPMStateChange()
-
- biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
- assertThat(isFaceAuthEnabled()).isTrue()
+ assertThat(isFaceAuthEnabled()).isFalse()
- biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
+ // Value changes for another user
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
assertThat(isFaceAuthEnabled()).isFalse()
+
+ // Value changes for current user.
+ biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
+
+ assertThat(isFaceAuthEnabled()).isTrue()
+ }
+
+ @Test
+ fun userChange_biometricEnabledChange_handlesRaceCondition() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+ verify(biometricManager)
+ .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
+ val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
+ biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+
+ assertThat(isFaceAuthEnabled()).isTrue()
}
@Test
@@ -382,7 +401,7 @@
}
@Test
- fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
+ fun userInLockdownUsesAuthFlagsToDetermineValue() =
testScope.runTest {
createBiometricSettingsRepository()
@@ -405,6 +424,38 @@
assertThat(isUserInLockdown()).isTrue()
}
+ @Test
+ fun authFlagChangesForCurrentUserArePropagated() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+
+ val authFlags = collectLastValue(underTest.authenticationFlags)
+ // has default value.
+ val defaultStrongAuthValue = STRONG_AUTH_REQUIRED_AFTER_BOOT
+ assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
+
+ // change strong auth flags for another user.
+ // Combine with one more flag to check if we do the bitwise and
+ val inLockdown =
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+ onStrongAuthChanged(inLockdown, ANOTHER_USER_ID)
+
+ // Still false.
+ assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
+
+ // change strong auth flags for current user.
+ onStrongAuthChanged(inLockdown, PRIMARY_USER_ID)
+
+ assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
+
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, ANOTHER_USER_ID)
+
+ assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
+
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
+ }
+
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 1e465c7..68c33fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -530,6 +530,8 @@
verify(collapsedSet).setVisibility(R.id.actionPlayPause, ConstraintSet.VISIBLE)
assertThat(actionNext.isEnabled()).isTrue()
+ assertThat(actionNext.isFocusable()).isTrue()
+ assertThat(actionNext.isClickable()).isTrue()
assertThat(actionNext.contentDescription).isEqualTo("next")
verify(collapsedSet).setVisibility(R.id.actionNext, ConstraintSet.VISIBLE)
@@ -576,6 +578,8 @@
assertThat(actionPrev.isEnabled()).isFalse()
assertThat(actionPrev.drawable).isNull()
+ assertThat(actionPrev.isFocusable()).isFalse()
+ assertThat(actionPrev.isClickable()).isFalse()
verify(expandedSet).setVisibility(R.id.actionPrev, ConstraintSet.INVISIBLE)
assertThat(actionNext.isEnabled()).isFalse()
@@ -610,6 +614,8 @@
assertThat(actionNext.isEnabled()).isFalse()
assertThat(actionNext.drawable).isNull()
+ assertThat(actionNext.isFocusable()).isFalse()
+ assertThat(actionNext.isClickable()).isFalse()
verify(expandedSet).setVisibility(R.id.actionNext, ConstraintSet.INVISIBLE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 9b26d9c..c89897c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -22,6 +22,7 @@
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP;
import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE;
+import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
import static com.google.common.truth.Truth.assertThat;
@@ -94,10 +95,7 @@
@Before
public void setUp() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
- when(mMediaOutputController.isSubStatusSupported()).thenReturn(false);
when(mMediaOutputController.getMediaItemList()).thenReturn(mMediaItems);
- when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
@@ -126,85 +124,24 @@
}
@Test
- public void getItemCount_containExtraOneForPairNew() {
- assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size() + 1);
- }
-
- @Test
- public void getItemId_validPosition_returnCorrespondingId() {
- assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaDevices.get(
- 0).getId().hashCode());
- }
-
- @Test
- public void getItemId_invalidPosition_returnPosition() {
- int invalidPosition = mMediaDevices.size() + 1;
- assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
- }
-
- @Test
- public void advanced_getItemCount_returnsMediaItemSize() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void getItemCount_returnsMediaItemSize() {
assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaItems.size());
}
@Test
- public void advanced_getItemId_validPosition_returnCorrespondingId() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void getItemId_validPosition_returnCorrespondingId() {
assertThat(mMediaOutputAdapter.getItemId(0)).isEqualTo(mMediaItems.get(
0).getMediaDevice().get().getId().hashCode());
}
@Test
- public void advanced_getItemId_invalidPosition_returnPosition() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void getItemId_invalidPosition_returnPosition() {
int invalidPosition = mMediaItems.size() + 1;
assertThat(mMediaOutputAdapter.getItemId(invalidPosition)).isEqualTo(invalidPosition);
}
@Test
public void onBindViewHolder_bindPairNew_verifyView() {
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
-
- assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mTitleText.getText()).isEqualTo(mContext.getText(
- R.string.media_output_dialog_pairing_new));
- }
-
- @Test
- public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
- mMediaOutputAdapter.getItemCount();
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
-
- assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- when(mMediaOutputController.getSessionName()).thenReturn(null);
- mMediaOutputAdapter.getItemCount();
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
-
- assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void advanced_onBindViewHolder_bindPairNew_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
@@ -222,8 +159,7 @@
}
@Test
- public void advanced_onBindViewHolder_bindGroup_withSessionName_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
@@ -243,8 +179,7 @@
}
@Test
- public void advanced_onBindViewHolder_bindGroup_noSessionName_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
@@ -277,8 +212,7 @@
}
@Test
- public void advanced_onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
@@ -294,8 +228,7 @@
}
@Test
- public void advanced_onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of(mMediaDevice2));
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
@@ -314,8 +247,7 @@
}
@Test
- public void advanced_onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of());
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
@@ -347,8 +279,7 @@
}
@Test
- public void advanced_onBindViewHolder_isMutingExpectedDevice_verifyView() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onBindViewHolder_isMutingExpectedDevice_verifyView() {
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
@@ -449,8 +380,6 @@
@Test
public void subStatusSupported_onBindViewHolder_bindHostDeviceWithOngoingSession_verifyView() {
- when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).thenReturn(true);
when(mMediaDevice1.isHostForOngoingSession()).thenReturn(true);
when(mMediaDevice1.hasSubtext()).thenReturn(true);
@@ -480,8 +409,6 @@
public void subStatusSupported_onBindViewHolder_bindDeviceRequirePremium_verifyView() {
String deviceStatus = (String) mContext.getText(
R.string.media_output_status_require_premium);
- when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaDevice2.hasSubtext()).thenReturn(true);
when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
@@ -506,8 +433,6 @@
public void subStatusSupported_onBindViewHolder_bindDeviceWithAdPlaying_verifyView() {
String deviceStatus = (String) mContext.getText(
R.string.media_output_status_try_after_ad);
- when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaDevice2.hasSubtext()).thenReturn(true);
when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_AD_ROUTING_DISALLOWED);
when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
@@ -531,8 +456,6 @@
@Test
public void subStatusSupported_onBindViewHolder_bindDeviceWithOngoingSession_verifyView() {
- when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaDevice1.hasSubtext()).thenReturn(true);
when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
@@ -603,15 +526,6 @@
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
- mViewHolder.mContainerLayout.performClick();
-
- verify(mMediaOutputController).launchBluetoothPairing(mViewHolder.mContainerLayout);
- }
-
- @Test
- public void advanced_onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mMediaOutputAdapter.updateItems();
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
@@ -629,6 +543,12 @@
when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(false);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+ when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter.updateItems();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
+ mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
mViewHolder.mContainerLayout.performClick();
@@ -641,7 +561,13 @@
when(mMediaOutputController.isCurrentOutputDeviceHasSessionOngoing()).thenReturn(true);
assertThat(mMediaDevice2.getState()).isEqualTo(
LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
+ when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_TRANSFER);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+ mMediaOutputAdapter.updateItems();
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
MediaOutputAdapter.MediaDeviceViewHolder spyMediaDeviceViewHolder = spy(mViewHolder);
+ mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 0);
mMediaOutputAdapter.onBindViewHolder(spyMediaDeviceViewHolder, 1);
@@ -665,20 +591,7 @@
}
@Test
- public void onItemClick_clicksSelectableDevice_triggerGrouping() {
- List<MediaDevice> selectableDevices = new ArrayList<>();
- selectableDevices.add(mMediaDevice2);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
-
- mViewHolder.mContainerLayout.performClick();
-
- verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
- }
-
- @Test
- public void advanced_onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
List<MediaDevice> selectableDevices = new ArrayList<>();
selectableDevices.add(mMediaDevice2);
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
@@ -692,8 +605,7 @@
}
@Test
- public void advanced_onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+ public void onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
ImmutableList.of(mMediaDevice2));
when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
@@ -710,21 +622,6 @@
@Test
public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- List<MediaDevice> selectableDevices = new ArrayList<>();
- selectableDevices.add(mMediaDevice1);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
- when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(true);
- mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
-
- mViewHolder.mContainerLayout.performClick();
-
- assertThat(mViewHolder.mSeekBar.isEnabled()).isFalse();
- }
-
- @Test
- public void advanced_onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(
mMediaItems.stream().map((item) -> item.getMediaDevice().get()).collect(
Collectors.toList()));
@@ -767,7 +664,6 @@
@Test
public void updateItems_controllerItemsUpdated_notUpdatesInAdapterUntilUpdateItems() {
- when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
mMediaOutputAdapter.updateItems();
List<MediaItem> updatedList = new ArrayList<>();
updatedList.add(new MediaItem());
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 8f7bad6..9f06b5f 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
@@ -76,7 +76,6 @@
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
@@ -200,8 +199,6 @@
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(false);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -402,24 +399,9 @@
}
@Test
- public void onDeviceListUpdate_verifyDeviceListCallback() {
- mMediaOutputController.start(mCb);
- reset(mCb);
-
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
- final List<MediaDevice> devices = new ArrayList<>(mMediaOutputController.getMediaDevices());
-
- assertThat(devices.containsAll(mMediaDevices)).isTrue();
- assertThat(devices.size()).isEqualTo(mMediaDevices.size());
- verify(mCb).onDeviceListChanged();
- }
-
- @Test
public void routeProcessSupport_onDeviceListUpdate_preferenceExist_NotUpdatesRangeInformation()
throws RemoteException {
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(true);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(true);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
mMediaOutputController.start(mCb);
reset(mCb);
@@ -431,8 +413,7 @@
}
@Test
- public void advanced_onDeviceListUpdate_verifyDeviceListCallback() {
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
+ public void onDeviceListUpdate_verifyDeviceListCallback() {
mMediaOutputController.start(mCb);
reset(mCb);
@@ -453,7 +434,6 @@
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
when(mMediaDevice1.getFeatures()).thenReturn(
ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
@@ -477,7 +457,6 @@
@Test
public void advanced_categorizeMediaItems_withSuggestedDevice_verifyDeviceListSize() {
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
when(mMediaDevice1.isSuggestedDevice()).thenReturn(true);
when(mMediaDevice2.isSuggestedDevice()).thenReturn(false);
@@ -515,7 +494,6 @@
@Test
public void advanced_onDeviceListUpdate_isRefreshing_updatesNeedRefreshToTrue() {
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
mMediaOutputController.start(mCb);
reset(mCb);
mMediaOutputController.mIsRefreshing = true;
@@ -717,7 +695,6 @@
@Test
public void isAnyDeviceTransferring_advancedLayoutSupport() {
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(true);
when(mMediaDevice1.getState()).thenReturn(
LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
mMediaOutputController.start(mCb);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index ca2b1da..349fac0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -22,6 +22,7 @@
import android.graphics.drawable.Drawable
import android.media.MediaRoute2Info
import android.os.PowerManager
+import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -210,7 +211,14 @@
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_START_CAST.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -246,7 +254,14 @@
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_ALMOST_CLOSE_TO_END_CAST.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -267,7 +282,14 @@
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_TRIGGERED.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -303,7 +325,14 @@
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE)
assertThat(uiEventLoggerFake.eventId(0))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_TRIGGERED.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -326,7 +355,14 @@
// Event index 1 since initially displaying the triggered chip would also log an event.
assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
- verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper, never())
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -403,7 +439,14 @@
// Event index 1 since initially displaying the triggered chip would also log an event.
assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
- verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper, never())
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -483,7 +526,14 @@
// Event index 1 since initially displaying the triggered chip would also log an event.
assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
@@ -511,7 +561,14 @@
// Event index 1 since initially displaying the triggered chip would also log an event.
assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
- verify(vibratorHelper).vibrate(any<VibrationEffect>())
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ any<VibrationEffect>(),
+ any(),
+ any<VibrationAttributes>(),
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 6e24941..d33271b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.temporarydisplay.chipbar
import android.os.PowerManager
+import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -461,7 +462,7 @@
}
@Test
- fun displayView_vibrationEffect_doubleClickEffect() {
+ fun displayView_vibrationEffect_doubleClickEffectWithHardwareFeedback() {
underTest.displayView(
createChipbarInfo(
Icon.Resource(R.id.check_box, null),
@@ -471,7 +472,14 @@
)
)
- verify(vibratorHelper).vibrate(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK))
+ verify(vibratorHelper)
+ .vibrate(
+ any(),
+ any(),
+ eq(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)),
+ any(),
+ eq(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)),
+ )
}
/** Regression test for b/266119467. */
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index 65735f0..4aaf347 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -17,10 +17,13 @@
package com.android.systemui.keyguard.data.repository
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
@@ -50,9 +53,12 @@
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
get() = _isFaceAuthSupportedInCurrentPosture
- private val _isCurrentUserInLockdown = MutableStateFlow(false)
override val isCurrentUserInLockdown: Flow<Boolean>
- get() = _isCurrentUserInLockdown
+ get() = _authFlags.map { it.isInUserLockdown }
+
+ private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
+ override val authenticationFlags: Flow<AuthenticationFlags>
+ get() = _authFlags
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
@@ -66,6 +72,10 @@
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
}
+ fun setAuthenticationFlags(value: AuthenticationFlags) {
+ _authFlags.value = value
+ }
+
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
_isFaceEnrolled.value = isFaceEnrolled
}
@@ -79,7 +89,22 @@
}
fun setIsUserInLockdown(value: Boolean) {
- _isCurrentUserInLockdown.value = value
+ if (value) {
+ setAuthenticationFlags(
+ AuthenticationFlags(
+ _authFlags.value.userId,
+ _authFlags.value.flag or
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+ )
+ )
+ } else {
+ setAuthenticationFlags(
+ AuthenticationFlags(
+ _authFlags.value.userId,
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ )
+ )
+ }
}
fun setIsNonStrongBiometricAllowed(value: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
new file mode 100644
index 0000000..b03b4ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBouncerMessageRepository.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
+import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeBouncerMessageRepository : BouncerMessageRepository {
+ private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
+ get() = _primaryAuthMessage
+
+ private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
+ get() = _faceAcquisitionMessage
+ private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
+ get() = _fingerprintAcquisitionMessage
+ private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val customMessage: StateFlow<BouncerMessageModel?>
+ get() = _customMessage
+ private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
+ get() = _biometricAuthMessage
+ private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val authFlagsMessage: StateFlow<BouncerMessageModel?>
+ get() = _authFlagsMessage
+
+ private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
+ override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
+ get() = _biometricLockedOutMessage
+
+ override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
+ _primaryAuthMessage.value = value
+ }
+
+ override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
+ _faceAcquisitionMessage.value = value
+ }
+
+ override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
+ _fingerprintAcquisitionMessage.value = value
+ }
+
+ override fun setCustomMessage(value: BouncerMessageModel?) {
+ _customMessage.value = value
+ }
+
+ fun setBiometricAuthMessage(value: BouncerMessageModel?) {
+ _biometricAuthMessage.value = value
+ }
+
+ fun setAuthFlagsMessage(value: BouncerMessageModel?) {
+ _authFlagsMessage.value = value
+ }
+
+ fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
+ _biometricLockedOutMessage.value = value
+ }
+
+ override fun clearMessage() {
+ _primaryAuthMessage.value = null
+ _faceAcquisitionMessage.value = null
+ _fingerprintAcquisitionMessage.value = null
+ _customMessage.value = null
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java
index 06a616c..994802d 100644
--- a/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java
@@ -74,9 +74,6 @@
public static final int TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE =
AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
- // Augmented autofill currently doesn't have an assigned request_id, use -2 as the magic number.
- public static final int AUGMENTED_AUTOFILL_REQUEST_ID = -2;
-
private final int mSessionId;
private Optional<FillRequestEventInternal> mEventInternal;
diff --git a/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java b/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
index 8f2ab71..fc5fb1a 100644
--- a/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/FillResponseEventLogger.java
@@ -16,17 +16,20 @@
package com.android.server.autofill;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
+
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_RESULT_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_SUCCESS;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__AUTHENTICATION_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__DATASET_AUTHENTICATION;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__AUTHENTICATION_TYPE__FULL_AUTHENTICATION;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__DIALOG;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__MENU;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_CANCELLED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_FAILURE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_RESPONSE_REPORTED__RESPONSE_STATUS__RESPONSE_STATUS_SESSION_DESTROYED;
@@ -218,7 +221,16 @@
public void maybeSetAvailableCount(int val) {
mEventInternal.ifPresent(event -> {
- event.mAvailableCount = val;
+ // Don't reset if it's already populated.
+ // This is just a technical limitation of not having complicated logic.
+ // Autofill Provider may return some datasets which are applicable to data types.
+ // In such a case, we set available count to the number of datasets provided.
+ // However, it's possible that those data types aren't detected by PCC, so in effect, there
+ // are 0 datasets. In the codebase, we treat it as null response, which may call this again
+ // to set 0. But we don't want to overwrite this value.
+ if (event.mAvailableCount == 0) {
+ event.mAvailableCount = val;
+ }
});
}
@@ -318,6 +330,50 @@
});
}
+ /**
+ * Set available_pcc_count.
+ */
+ public void maybeSetAvailablePccCount(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAvailablePccCount = val;
+ });
+ }
+
+ /**
+ * Set available_pcc_only_count.
+ */
+ public void maybeSetAvailablePccOnlyCount(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAvailablePccOnlyCount = val;
+ });
+ }
+
+ /**
+ * Set available_pcc_count.
+ */
+ public void maybeSetAvailableDatasetsPccCount(@Nullable List<Dataset> datasetList) {
+ mEventInternal.ifPresent(event -> {
+ int pccOnlyCount = 0;
+ int pccCount = 0;
+ if (datasetList != null) {
+ for (int i = 0; i < datasetList.size(); i++) {
+ Dataset dataset = datasetList.get(i);
+ if (dataset != null) {
+ if (dataset.getEligibleReason() == PICK_REASON_PCC_DETECTION_ONLY) {
+ pccOnlyCount++;
+ pccCount++;
+ } else if (dataset.getEligibleReason()
+ == PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER) {
+ pccCount++;
+ }
+ }
+ }
+ }
+ event.mAvailablePccOnlyCount = pccOnlyCount;
+ event.mAvailablePccCount = pccCount;
+ });
+ }
+
/**
* Log an AUTOFILL_FILL_RESPONSE_REPORTED event.
@@ -344,7 +400,9 @@
+ " mLatencyAuthenticationUiDisplayMillis=" + event.mLatencyAuthenticationUiDisplayMillis
+ " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis
+ " mResponseStatus=" + event.mResponseStatus
- + " mLatencyResponseProcessingMillis=" + event.mLatencyResponseProcessingMillis);
+ + " mLatencyResponseProcessingMillis=" + event.mLatencyResponseProcessingMillis
+ + " mAvailablePccCount=" + event.mAvailablePccCount
+ + " mAvailablePccOnlyCount=" + event.mAvailablePccOnlyCount);
}
FrameworkStatsLog.write(
AUTOFILL_FILL_RESPONSE_REPORTED,
@@ -361,7 +419,9 @@
event.mLatencyAuthenticationUiDisplayMillis,
event.mLatencyDatasetDisplayMillis,
event.mResponseStatus,
- event.mLatencyResponseProcessingMillis);
+ event.mLatencyResponseProcessingMillis,
+ event.mAvailablePccCount,
+ event.mAvailablePccOnlyCount);
mEventInternal = Optional.empty();
}
@@ -379,6 +439,8 @@
int mLatencyDatasetDisplayMillis = 0;
int mResponseStatus = RESPONSE_STATUS_UNKNOWN;
int mLatencyResponseProcessingMillis = 0;
+ int mAvailablePccCount;
+ int mAvailablePccOnlyCount;
FillResponseEventInternal() {
}
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index ca743cb..b2f9a93 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -16,6 +16,8 @@
package com.android.server.autofill;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
@@ -46,6 +48,12 @@
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_NO_PCC;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PCC_DETECTION_ONLY;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PROVIDER_DETECTION_ONLY;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_UNKNOWN;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
@@ -116,6 +124,22 @@
public @interface AuthenticationResult {
}
+ /**
+ * Reasons why the picked dataset was present. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillPresentationEventReported.DatasetPickedReason}.
+ * This enum is similar to {@link android.service.autofill.Dataset.DatasetEligibleReason}
+ */
+ @IntDef(prefix = {"PICK_REASON"}, value = {
+ PICK_REASON_UNKNOWN,
+ PICK_REASON_NO_PCC,
+ PICK_REASON_PROVIDER_DETECTION_ONLY,
+ PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC,
+ PICK_REASON_PCC_DETECTION_ONLY,
+ PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DatasetPickedReason {}
+
public static final int NOT_SHOWN_REASON_ANY_SHOWN =
AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN;
public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED =
@@ -151,6 +175,18 @@
public static final int AUTHENTICATION_RESULT_FAILURE =
AUTOFILL_PRESENTATION_EVENT_REPORTED__AUTHENTICATION_RESULT__AUTHENTICATION_FAILURE;
+ public static final int PICK_REASON_UNKNOWN =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_UNKNOWN;
+ public static final int PICK_REASON_NO_PCC =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_NO_PCC;
+ public static final int PICK_REASON_PROVIDER_DETECTION_ONLY =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PROVIDER_DETECTION_ONLY;
+ public static final int PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
+ public static final int PICK_REASON_PCC_DETECTION_ONLY =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PCC_DETECTION_ONLY;
+ public static final int PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER =
+ AUTOFILL_PRESENTATION_EVENT_REPORTED__SELECTED_DATASET_PICKED_REASON__PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
private final int mSessionId;
private Optional<PresentationStatsEventInternal> mEventInternal;
@@ -194,36 +230,61 @@
public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList,
AutofillId currentViewId) {
mEventInternal.ifPresent(event -> {
- int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId);
- event.mAvailableCount = availableCount;
- event.mIsDatasetAvailable = availableCount > 0;
+ CountContainer container = getDatasetCountForAutofillId(datasetList, currentViewId);
+ event.mAvailableCount = container.mAvailableCount;
+ event.mAvailablePccCount = container.mAvailablePccCount;
+ event.mAvailablePccOnlyCount = container.mAvailablePccOnlyCount;
+ event.mIsDatasetAvailable = container.mAvailableCount > 0;
});
}
public void maybeSetCountShown(@Nullable List<Dataset> datasetList,
AutofillId currentViewId) {
mEventInternal.ifPresent(event -> {
- int countShown = getDatasetCountForAutofillId(datasetList, currentViewId);
- event.mCountShown = countShown;
- if (countShown > 0) {
+ CountContainer container = getDatasetCountForAutofillId(datasetList, currentViewId);
+ event.mCountShown = container.mAvailableCount;
+ if (container.mAvailableCount > 0) {
event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN;
}
});
}
- private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList,
+ private static CountContainer getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList,
AutofillId currentViewId) {
- int availableCount = 0;
+
+ CountContainer container = new CountContainer();
if (datasetList != null) {
for (int i = 0; i < datasetList.size(); i++) {
Dataset data = datasetList.get(i);
if (data != null && data.getFieldIds() != null
&& data.getFieldIds().contains(currentViewId)) {
- availableCount += 1;
+ container.mAvailableCount += 1;
+ if (data.getEligibleReason() == PICK_REASON_PCC_DETECTION_ONLY) {
+ container.mAvailablePccOnlyCount++;
+ container.mAvailablePccCount++;
+ } else if (data.getEligibleReason()
+ == PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER) {
+ container.mAvailablePccCount++;
+ }
}
}
}
- return availableCount;
+ return container;
+ }
+
+ private static class CountContainer{
+ int mAvailableCount = 0;
+ int mAvailablePccCount = 0;
+ int mAvailablePccOnlyCount = 0;
+
+ CountContainer() {}
+
+ CountContainer(int availableCount, int availablePccCount,
+ int availablePccOnlyCount) {
+ mAvailableCount = availableCount;
+ mAvailablePccCount = availablePccCount;
+ mAvailablePccOnlyCount = availablePccOnlyCount;
+ }
}
public void maybeSetCountFilteredUserTyping(int countFilteredUserTyping) {
@@ -375,6 +436,46 @@
});
}
+ /**
+ * Set available_pcc_count.
+ */
+ public void maybeSetAvailablePccCount(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAvailablePccCount = val;
+ });
+ }
+
+ /**
+ * Set available_pcc_only_count.
+ */
+ public void maybeSetAvailablePccOnlyCount(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAvailablePccOnlyCount = val;
+ });
+ }
+
+ /**
+ * Set selected_dataset_picked_reason.
+ */
+ public void maybeSetSelectedDatasetPickReason(@Dataset.DatasetEligibleReason int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mSelectedDatasetPickedReason = convertDatasetPickReason(val);
+ });
+ }
+
+ private int convertDatasetPickReason(@Dataset.DatasetEligibleReason int val) {
+ switch (val) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ return val;
+ }
+ return PICK_REASON_UNKNOWN;
+ }
+
public void logAndEndEvent() {
if (!mEventInternal.isPresent()) {
@@ -410,7 +511,10 @@
+ " mAuthenticationResult=" + event.mAuthenticationResult
+ " mLatencyAuthenticationUiDisplayMillis="
+ event.mLatencyAuthenticationUiDisplayMillis
- + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis);
+ + " mLatencyDatasetDisplayMillis=" + event.mLatencyDatasetDisplayMillis
+ + " mAvailablePccCount=" + event.mAvailablePccCount
+ + " mAvailablePccOnlyCount=" + event.mAvailablePccOnlyCount
+ + " mSelectedDatasetPickedReason=" + event.mSelectedDatasetPickedReason);
}
// TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -443,7 +547,10 @@
event.mAuthenticationType,
event.mAuthenticationResult,
event.mLatencyAuthenticationUiDisplayMillis,
- event.mLatencyDatasetDisplayMillis);
+ event.mLatencyDatasetDisplayMillis,
+ event.mAvailablePccCount,
+ event.mAvailablePccOnlyCount,
+ event.mSelectedDatasetPickedReason);
mEventInternal = Optional.empty();
}
@@ -472,6 +579,9 @@
int mAuthenticationResult = AUTHENTICATION_RESULT_UNKNOWN;
int mLatencyAuthenticationUiDisplayMillis = -1;
int mLatencyDatasetDisplayMillis = -1;
+ int mAvailablePccCount = -1;
+ int mAvailablePccOnlyCount = -1;
+ @DatasetPickedReason int mSelectedDatasetPickedReason = PICK_REASON_UNKNOWN;
PresentationStatsEventInternal() {}
}
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
index e5435c2..28e8e30 100644
--- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -252,6 +252,15 @@
}
/**
+ * Set is_framework_created_save_info as long as mEventInternal presents.
+ */
+ public void maybeSetIsFrameworkCreatedSaveInfo(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsFrameworkCreatedSaveInfo = val;
+ });
+ }
+
+ /**
* Log an AUTOFILL_SAVE_EVENT_REPORTED event.
*/
public void logAndEndEvent() {
@@ -277,7 +286,8 @@
+ " mIsSaved=" + event.mIsSaved
+ " mLatencySaveUiDisplayMillis=" + event.mLatencySaveUiDisplayMillis
+ " mLatencySaveRequestMillis=" + event.mLatencySaveRequestMillis
- + " mLatencySaveFinishMillis=" + event.mLatencySaveFinishMillis);
+ + " mLatencySaveFinishMillis=" + event.mLatencySaveFinishMillis
+ + " mIsFrameworkCreatedSaveInfo=" + event.mIsFrameworkCreatedSaveInfo);
}
FrameworkStatsLog.write(
AUTOFILL_SAVE_EVENT_REPORTED,
@@ -295,7 +305,8 @@
event.mIsSaved,
event.mLatencySaveUiDisplayMillis,
event.mLatencySaveRequestMillis,
- event.mLatencySaveFinishMillis);
+ event.mLatencySaveFinishMillis,
+ event.mIsFrameworkCreatedSaveInfo);
mEventInternal = Optional.empty();
}
@@ -314,6 +325,7 @@
long mLatencySaveUiDisplayMillis = 0;
long mLatencySaveRequestMillis = 0;
long mLatencySaveFinishMillis = 0;
+ boolean mIsFrameworkCreatedSaveInfo = false;
SaveEventInternal() {
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 192fdfe..2d03daa 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,12 @@
import static android.Manifest.permission.PROVIDE_OWN_AUTOFILL_SUGGESTIONS;
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.AutofillService.EXTRA_FILL_RESPONSE;
+import static android.service.autofill.Dataset.PICK_REASON_NO_PCC;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_ONLY;
+import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
+import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY;
+import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
+import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
@@ -36,6 +42,7 @@
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
+import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
@@ -79,7 +86,6 @@
import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE;
import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET;
import static com.android.server.autofill.SaveEventLogger.SAVE_UI_SHOWN_REASON_UNKNOWN;
-import static com.android.server.autofill.SessionCommittedEventLogger.COMMIT_REASON_SESSION_DESTROYED;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -124,6 +130,7 @@
import android.service.autofill.AutofillService;
import android.service.autofill.CompositeUserData;
import android.service.autofill.Dataset;
+import android.service.autofill.Dataset.DatasetEligibleReason;
import android.service.autofill.FieldClassification;
import android.service.autofill.FieldClassification.Match;
import android.service.autofill.FieldClassificationUserData;
@@ -1158,7 +1165,7 @@
}
mSessionFlags.mAugmentedAutofillOnly = true;
mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID);
- mFillRequestEventLogger.maybeSetIsAugmented(mSessionFlags.mAugmentedAutofillOnly);
+ mFillRequestEventLogger.maybeSetIsAugmented(true);
mFillRequestEventLogger.logAndEndEvent();
triggerAugmentedAutofillLocked(flags);
return;
@@ -1554,9 +1561,8 @@
Slog.d(TAG, message.toString());
}
}
-
- if (((response.getDatasets() == null || response.getDatasets().isEmpty())
- && response.getAuthentication() == null)
+ List<Dataset> datasetList = response.getDatasets();
+ if (((datasetList == null || datasetList.isEmpty()) && response.getAuthentication() == null)
|| autofillDisabled) {
// Response is "empty" from a UI point of view, need to notify client.
notifyUnavailableToClient(
@@ -1578,6 +1584,9 @@
}
}
+ mFillResponseEventLogger.maybeSetAvailableCount(
+ datasetList == null ? 0 : datasetList.size());
+
// TODO(b/266379948): Ideally wait for PCC request to finish for a while more
// (say 100ms) before proceeding further on.
@@ -1728,6 +1737,7 @@
}
if (ids.isEmpty()) return saveInfo;
AutofillId[] autofillIds = new AutofillId[ids.size()];
+ mSaveEventLogger.maybeSetIsFrameworkCreatedSaveInfo(true);
ids.toArray(autofillIds);
return SaveInfo.copy(saveInfo, autofillIds);
}
@@ -1798,6 +1808,13 @@
private void computeDatasetsForProviderAndUpdateContainer(
FillResponse response, DatasetComputationContainer container) {
+ @DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
+ boolean isPccEnabled = mService.isPccClassificationEnabled();
+ if (isPccEnabled) {
+ globalPickReason = PICK_REASON_PROVIDER_DETECTION_ONLY;
+ } else {
+ globalPickReason = PICK_REASON_NO_PCC;
+ }
List<Dataset> datasets = response.getDatasets();
if (datasets == null) return;
ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
@@ -1805,6 +1822,7 @@
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
for (Dataset dataset : response.getDatasets()) {
if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
+ @DatasetEligibleReason int pickReason = globalPickReason;
if (dataset.getAutofillDatatypes() != null
&& !dataset.getAutofillDatatypes().isEmpty()) {
// This dataset has information relevant for detection too, so we should filter
@@ -1827,6 +1845,7 @@
if (newSize == 0) continue;
if (conversionRequired) {
+ pickReason = PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
ArrayList<AutofillId> fieldIds = new ArrayList<>(newSize);
ArrayList<AutofillValue> fieldValues = new ArrayList<>(newSize);
ArrayList<RemoteViews> fieldPresentations = new ArrayList<>(newSize);
@@ -1870,6 +1889,7 @@
dataset.getAuthentication());
}
}
+ dataset.setEligibleReasonReason(pickReason);
eligibleDatasets.add(dataset);
for (AutofillId id : dataset.getFieldIds()) {
eligibleAutofillIds.add(id);
@@ -1905,6 +1925,8 @@
Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
for (int i = 0; i < datasets.size(); i++) {
+
+ @DatasetEligibleReason int pickReason = PICK_REASON_PCC_DETECTION_ONLY;
Dataset dataset = datasets.get(i);
if (dataset.getAutofillDatatypes() == null
|| dataset.getAutofillDatatypes().isEmpty()) continue;
@@ -1919,7 +1941,12 @@
Set<AutofillId> datasetAutofillIds = new ArraySet<>();
for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
- if (dataset.getAutofillDatatypes().get(j) == null) continue;
+ if (dataset.getAutofillDatatypes().get(j) == null) {
+ if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
+ pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
+ }
+ continue;
+ }
String hint = dataset.getAutofillDatatypes().get(j);
if (hintsToAutofillIdMap.containsKey(hint)) {
@@ -1966,6 +1993,7 @@
null,
dataset.getId(),
dataset.getAuthentication());
+ dataset.setEligibleReasonReason(pickReason);
eligibleDatasets.add(newDataset);
Set<Dataset> newDatasets;
for (AutofillId autofillId : datasetAutofillIds) {
@@ -2229,7 +2257,6 @@
@Override
public void save() {
synchronized (mLock) {
- mSaveEventLogger.maybeSetSaveButtonClicked(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#save() rejected - session: "
+ id + " destroyed");
@@ -2248,7 +2275,6 @@
public void cancelSave() {
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
- mSaveEventLogger.maybeSetDialogDismissed(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
+ id + " destroyed");
@@ -2707,6 +2733,7 @@
mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
Event.NO_SAVE_UI_REASON_NONE,
COMMIT_REASON_UNKNOWN));
+ logAllEvents(COMMIT_REASON_UNKNOWN);
}
/**
@@ -2720,6 +2747,7 @@
@AutofillCommitReason int commitReason) {
mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this,
saveDialogNotShowReason, commitReason));
+ logAllEvents(commitReason);
}
private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason,
@@ -2951,6 +2979,7 @@
changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
mComponentName, mCompatMode, saveDialogNotShowReason);
+ logAllEvents(commitReason);
}
/**
@@ -3403,7 +3432,7 @@
getUiForShowing().showSaveUi(serviceLabel, serviceIcon,
mService.getServicePackageName(), saveInfo, this,
mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode,
- response.getShowSaveDialogIcon());
+ response.getShowSaveDialogIcon(), mSaveEventLogger);
mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis(
SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp);
if (client != null) {
@@ -4015,8 +4044,6 @@
mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
mPresentationStatsEventLogger.maybeSetAvailableCount(
response.getDatasets(), mCurrentViewId);
- mFillResponseEventLogger.maybeSetAvailableCount(
- response.getDatasets(), mCurrentViewId);
}
if (isSameViewEntered) {
@@ -4388,7 +4415,7 @@
getUiForShowing().showFillDialog(filledId, response, filterText,
mService.getServicePackageName(), mComponentName, serviceIcon, this,
- id, mCompatMode);
+ id, mCompatMode, mPresentationStatsEventLogger);
return true;
}
@@ -4707,6 +4734,7 @@
autofillableIds = null;
}
// Log the existing FillResponse event.
+ mFillResponseEventLogger.maybeSetAvailableCount(0);
mFillResponseEventLogger.logAndEndEvent();
mService.resetLastResponse();
@@ -4818,6 +4846,7 @@
mFillRequestEventLogger.maybeSetAppPackageUid(uid);
mFillRequestEventLogger.maybeSetFlags(mFlags);
mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID);
+ mFillRequestEventLogger.maybeSetIsAugmented(true);
mFillRequestEventLogger.logAndEndEvent();
final ViewState viewState = mViewStates.get(mCurrentViewId);
@@ -4934,10 +4963,10 @@
mResponses.put(requestId, newResponse);
mClientState = newClientState != null ? newClientState : newResponse.getClientState();
- mPresentationStatsEventLogger.maybeSetAvailableCount(
- newResponse.getDatasets(), mCurrentViewId);
- mFillResponseEventLogger.maybeSetAvailableCount(
- newResponse.getDatasets(), mCurrentViewId);
+ List<Dataset> datasetList = newResponse.getDatasets();
+
+ mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId);
+ mFillResponseEventLogger.maybeSetAvailableDatasetsPccCount(datasetList);
setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, false);
updateFillDialogTriggerIdsLocked();
@@ -5062,6 +5091,8 @@
if (generateEvent) {
mService.logDatasetSelected(dataset.getId(), id, mClientState, uiType);
mPresentationStatsEventLogger.maybeSetSelectedDatasetId(datasetIndex);
+ mPresentationStatsEventLogger.maybeSetSelectedDatasetPickReason(
+ dataset.getEligibleReason());
}
if (mCurrentViewId != null) {
mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
@@ -5655,6 +5686,19 @@
}
}
+ @GuardedBy("mLock")
+ private void logAllEvents(@AutofillCommitReason int val) {
+ mSessionCommittedEventLogger.maybeSetCommitReason(val);
+ mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
+ mSessionCommittedEventLogger.maybeSetSessionDurationMillis(
+ SystemClock.elapsedRealtime() - mStartTime);
+ mFillRequestEventLogger.logAndEndEvent();
+ mFillResponseEventLogger.logAndEndEvent();
+ mPresentationStatsEventLogger.logAndEndEvent();
+ mSaveEventLogger.logAndEndEvent();
+ mSessionCommittedEventLogger.logAndEndEvent();
+ }
+
/**
* Destroy this session and perform any clean up work.
*
@@ -5669,15 +5713,7 @@
@GuardedBy("mLock")
RemoteFillService destroyLocked() {
// Log unlogged events.
- mSessionCommittedEventLogger.maybeSetCommitReason(COMMIT_REASON_SESSION_DESTROYED);
- mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount);
- mSessionCommittedEventLogger.maybeSetSessionDurationMillis(
- SystemClock.elapsedRealtime() - mStartTime);
- mSessionCommittedEventLogger.logAndEndEvent();
- mPresentationStatsEventLogger.logAndEndEvent();
- mSaveEventLogger.logAndEndEvent();
- mFillResponseEventLogger.logAndEndEvent();
- mFillRequestEventLogger.logAndEndEvent();
+ logAllEvents(COMMIT_REASON_SESSION_DESTROYED);
if (mDestroyed) {
return null;
diff --git a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
index 541ec80..cd37073 100644
--- a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
@@ -16,13 +16,8 @@
package com.android.server.autofill;
+import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_ACTIVITY_FINISHED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_SESSION_DESTROYED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CHANGED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CLICKED;
-import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_COMMITTED;
import static com.android.server.autofill.Helper.sVerbose;
import android.annotation.IntDef;
@@ -32,7 +27,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
-
+import android.view.autofill.AutofillManager.AutofillCommitReason;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
@@ -45,35 +40,6 @@
public final class SessionCommittedEventLogger {
private static final String TAG = "SessionCommittedEventLogger";
- /**
- * Reasons why presentation was not shown. These are wrappers around
- * {@link com.android.os.AtomsProto.AutofillSessionCommitted.AutofillCommitReason}.
- */
- @IntDef(prefix = {"COMMIT_REASON"}, value = {
- COMMIT_REASON_UNKNOWN,
- COMMIT_REASON_ACTIVITY_FINISHED,
- COMMIT_REASON_VIEW_COMMITTED,
- COMMIT_REASON_VIEW_CLICKED,
- COMMIT_REASON_VIEW_CHANGED,
- COMMIT_REASON_SESSION_DESTROYED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface CommitReason {
- }
-
- public static final int COMMIT_REASON_UNKNOWN =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_UNKNOWN;
- public static final int COMMIT_REASON_ACTIVITY_FINISHED =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_ACTIVITY_FINISHED;
- public static final int COMMIT_REASON_VIEW_COMMITTED =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_COMMITTED;
- public static final int COMMIT_REASON_VIEW_CLICKED =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CLICKED;
- public static final int COMMIT_REASON_VIEW_CHANGED =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CHANGED;
- public static final int COMMIT_REASON_SESSION_DESTROYED =
- AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_SESSION_DESTROYED;
-
private final int mSessionId;
private Optional<SessionCommittedEventInternal> mEventInternal;
@@ -110,9 +76,9 @@
/**
* Set commit_reason as long as mEventInternal presents.
*/
- public void maybeSetCommitReason(@CommitReason int val) {
+ public void maybeSetCommitReason(@AutofillCommitReason int val) {
mEventInternal.ifPresent(event -> {
- event.mCommitReason = val;
+ event.mCommitReason = val;
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index b3cbe45..f92d38d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -52,6 +52,8 @@
import com.android.server.UiModeManagerInternal;
import com.android.server.UiThread;
import com.android.server.autofill.Helper;
+import com.android.server.autofill.PresentationStatsEventLogger;
+import com.android.server.autofill.SaveEventLogger;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
@@ -326,7 +328,7 @@
@NonNull ValueFinder valueFinder, @NonNull ComponentName componentName,
@NonNull AutoFillUiCallback callback, @NonNull Context context,
@NonNull PendingUi pendingSaveUi, boolean isUpdate, boolean compatMode,
- boolean showServiceIcon) {
+ boolean showServiceIcon, @Nullable SaveEventLogger mSaveEventLogger) {
if (sVerbose) {
Slogf.v(TAG, "showSaveUi(update=%b) for %s and display %d: %s", isUpdate,
componentName.toShortString(), context.getDisplayId(), info);
@@ -355,6 +357,9 @@
@Override
public void onSave() {
log.setType(MetricsEvent.TYPE_ACTION);
+ if (mSaveEventLogger != null) {
+ mSaveEventLogger.maybeSetSaveButtonClicked(true);
+ }
hideSaveUiUiThread(callback);
callback.save();
destroySaveUiUiThread(pendingSaveUi, true);
@@ -363,6 +368,9 @@
@Override
public void onCancel(IntentSender listener) {
log.setType(MetricsEvent.TYPE_DISMISS);
+ if (mSaveEventLogger != null) {
+ mSaveEventLogger.maybeSetCancelButtonClicked(true);
+ }
hideSaveUiUiThread(callback);
if (listener != null) {
try {
@@ -384,6 +392,9 @@
callback.cancelSave();
}
mMetricsLogger.write(log);
+ if (mSaveEventLogger != null) {
+ mSaveEventLogger.maybeSetDialogDismissed(true);
+ }
}
@Override
@@ -400,7 +411,8 @@
public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response,
@Nullable String filterText, @Nullable String servicePackageName,
@NonNull ComponentName componentName, @Nullable Drawable serviceIcon,
- @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode) {
+ @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode,
+ @Nullable PresentationStatsEventLogger mPresentationStatsEventLogger) {
if (sVerbose) {
Slog.v(TAG, "showFillDialog for "
+ componentName.toShortString() + ": " + response);
@@ -442,6 +454,10 @@
@Override
public void onDatasetPicked(Dataset dataset) {
log(MetricsEvent.TYPE_ACTION);
+ if (mPresentationStatsEventLogger != null) {
+ mPresentationStatsEventLogger.maybeSetPositiveCtaButtonClicked(
+ true);
+ }
hideFillDialogUiThread(callback);
if (mCallback != null) {
final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -453,6 +469,9 @@
@Override
public void onDismissed() {
log(MetricsEvent.TYPE_DISMISS);
+ if (mPresentationStatsEventLogger != null) {
+ mPresentationStatsEventLogger.maybeSetDialogDismissed(true);
+ }
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
callback.requestFallbackFromFillDialog();
@@ -461,6 +480,10 @@
@Override
public void onCanceled() {
log(MetricsEvent.TYPE_CLOSE);
+ if (mPresentationStatsEventLogger != null) {
+ mPresentationStatsEventLogger.maybeSetNegativeCtaButtonClicked(
+ true);
+ }
hideFillDialogUiThread(callback);
callback.requestShowSoftInput(focusedId);
callback.requestFallbackFromFillDialog();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
index 8570515..a2b71e0 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
@@ -16,6 +16,7 @@
package com.android.server.companion;
+import android.os.Binder;
import android.provider.DeviceConfig;
/**
@@ -34,7 +35,12 @@
* Returns whether the given flag is currently enabled, with a default value of {@code false}.
*/
public static boolean isEnabled(String flag) {
- return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index ed61d64..6b99494 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1383,10 +1383,11 @@
}
@Override
- public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
+ public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback,
+ @CrossDeviceSyncControllerCallback.Type int type) {
if (CompanionDeviceConfig.isEnabled(
CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
- mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback);
+ mCrossDeviceSyncController.registerCallMetadataSyncCallback(callback, type);
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index 3b108e6..c5ef4e4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -36,7 +36,8 @@
* Registers a callback from an InCallService / ConnectionService to CDM to process sync
* requests and perform call control actions.
*/
- void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback);
+ void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback,
+ @CrossDeviceSyncControllerCallback.Type int type);
/**
* Requests a sync from an InCallService / ConnectionService to CDM, for the given association
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
index 459bf98..7371824 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionService.java
@@ -17,6 +17,7 @@
package com.android.server.companion.datatransfer.contextsync;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Bundle;
import android.telecom.Call;
import android.telecom.Connection;
@@ -62,20 +63,14 @@
final CallMetadataSyncConnection existingConnection =
mActiveConnections.get(new CallMetadataSyncConnectionIdentifier(
associationId, call.getId()));
- if (existingConnection == null) {
- final Bundle extras = new Bundle();
- extras.putInt(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID,
- associationId);
- extras.putParcelable(CrossDeviceSyncController.EXTRA_CALL, call);
- mTelecomManager.addNewIncomingCall(call.getPhoneAccountHandle(),
- extras);
- } else {
+ if (existingConnection != null) {
existingConnection.update(call);
}
}
// Remove obsolete calls.
mActiveConnections.values().removeIf(connection -> {
- if (!callMetadataSyncData.hasCall(connection.getCallId())) {
+ if (associationId == connection.getAssociationId()
+ && !callMetadataSyncData.hasCall(connection.getCallId())) {
connection.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
return true;
}
@@ -91,7 +86,8 @@
mAudioManager = getSystemService(AudioManager.class);
mTelecomManager = getSystemService(TelecomManager.class);
mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class);
- mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback);
+ mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback,
+ CrossDeviceSyncControllerCallback.TYPE_CONNECTION_SERVICE);
}
@Override
@@ -101,6 +97,11 @@
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
final CallMetadataSyncData.Call call = connectionRequest.getExtras().getParcelable(
CrossDeviceSyncController.EXTRA_CALL, CallMetadataSyncData.Call.class);
+ // InCallServices outside of framework (like Dialer's) might try to read this, and crash
+ // when they can't. Remove it once we're done with it, as well as the other internal ones.
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID);
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
final CallMetadataSyncConnection connection = new CallMetadataSyncConnection(
mTelecomManager,
mAudioManager,
@@ -113,15 +114,17 @@
CrossDeviceSyncController.createCallControlMessage(callId, action));
}
});
- connection.setConnectionProperties(
- Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED);
+ connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
+ connection.setInitializing();
return connection;
}
@Override
public void onCreateIncomingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
ConnectionRequest connectionRequest) {
- Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
+ final String id =
+ phoneAccountHandle != null ? phoneAccountHandle.getId() : "unknown PhoneAccount";
+ Slog.e(TAG, "onCreateOutgoingConnectionFailed for: " + id);
}
@Override
@@ -132,7 +135,6 @@
final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
call.setId(UUID.randomUUID().toString());
call.setStatus(android.companion.Telecom.Call.UNKNOWN_STATUS);
- call.setPhoneAccountHandle(phoneAccountHandle);
final CallMetadataSyncData.CallFacilitator callFacilitator =
new CallMetadataSyncData.CallFacilitator(phoneAccount.getLabel().toString(),
phoneAccount.getExtras().getString(
@@ -142,6 +144,10 @@
final int associationId = connectionRequest.getExtras().getInt(
CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL);
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_CALL_FACILITATOR_ID);
+ connectionRequest.getExtras().remove(CrossDeviceSyncController.EXTRA_ASSOCIATION_ID);
+
final CallMetadataSyncConnection connection = new CallMetadataSyncConnection(
mTelecomManager,
mAudioManager,
@@ -154,8 +160,7 @@
CrossDeviceSyncController.createCallControlMessage(callId, action));
}
});
- connection.setConnectionProperties(
- Connection.PROPERTY_IS_EXTERNAL_CALL | Connection.PROPERTY_SELF_MANAGED);
+ connection.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
mCdmsi.sendCrossDeviceSyncMessage(associationId,
CrossDeviceSyncController.createCallCreateMessage(call.getId(),
@@ -168,13 +173,21 @@
@Override
public void onCreateOutgoingConnectionFailed(PhoneAccountHandle phoneAccountHandle,
ConnectionRequest connectionRequest) {
- Slog.e(TAG, "onCreateIncomingConnectionFailed for: " + phoneAccountHandle.getId());
+ final String id =
+ phoneAccountHandle != null ? phoneAccountHandle.getId() : "unknown PhoneAccount";
+ Slog.e(TAG, "onCreateOutgoingConnectionFailed for: " + id);
}
@Override
public void onCreateConnectionComplete(Connection connection) {
if (connection instanceof CallMetadataSyncConnection) {
- ((CallMetadataSyncConnection) connection).initialize();
+ final CallMetadataSyncConnection callMetadataSyncConnection =
+ (CallMetadataSyncConnection) connection;
+ callMetadataSyncConnection.initialize();
+ mActiveConnections.put(new CallMetadataSyncConnectionIdentifier(
+ callMetadataSyncConnection.getAssociationId(),
+ callMetadataSyncConnection.getCallId()),
+ callMetadataSyncConnection);
}
}
@@ -242,7 +255,11 @@
return mCall.getId();
}
- public void initialize() {
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ private void initialize() {
final int status = mCall.getStatus();
if (status == android.companion.Telecom.Call.RINGING_SILENCED) {
mTelecomManager.silenceRinger();
@@ -254,12 +271,21 @@
setActive();
} else if (state == Call.STATE_HOLDING) {
setOnHold();
+ } else if (state == Call.STATE_DISCONNECTED) {
+ setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
} else {
- Slog.e(TAG, "Could not initialize call to unknown state");
+ setInitialized();
+ }
+
+ final String callerId = mCall.getCallerId();
+ if (callerId != null) {
+ setCallerDisplayName(callerId, TelecomManager.PRESENTATION_ALLOWED);
+ setAddress(Uri.fromParts("custom", mCall.getCallerId(), null),
+ TelecomManager.PRESENTATION_ALLOWED);
}
final Bundle extras = new Bundle();
- extras.putString(CrossDeviceCall.EXTRA_CALL_ID, mCall.getId());
+ extras.putString(CrossDeviceSyncController.EXTRA_CALL_ID, mCall.getId());
putExtras(extras);
int capabilities = getConnectionCapabilities();
@@ -280,7 +306,7 @@
}
}
- public void update(CallMetadataSyncData.Call call) {
+ private void update(CallMetadataSyncData.Call call) {
final int status = call.getStatus();
if (status == android.companion.Telecom.Call.RINGING_SILENCED
&& mCall.getStatus() != android.companion.Telecom.Call.RINGING_SILENCED) {
@@ -295,31 +321,29 @@
setActive();
} else if (state == Call.STATE_HOLDING) {
setOnHold();
+ } else if (state == Call.STATE_DISCONNECTED) {
+ setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
} else {
Slog.e(TAG, "Could not update call to unknown state");
}
}
int capabilities = getConnectionCapabilities();
+ mCall.setControls(call.getControls());
final boolean hasHoldControl = mCall.hasControl(
android.companion.Telecom.PUT_ON_HOLD)
|| mCall.hasControl(android.companion.Telecom.TAKE_OFF_HOLD);
- if (hasHoldControl != ((getConnectionCapabilities() & CAPABILITY_HOLD)
- == CAPABILITY_HOLD)) {
- if (hasHoldControl) {
- capabilities |= CAPABILITY_HOLD;
- } else {
- capabilities &= ~CAPABILITY_HOLD;
- }
+ if (hasHoldControl) {
+ capabilities |= CAPABILITY_HOLD;
+ } else {
+ capabilities &= ~CAPABILITY_HOLD;
}
- final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE);
- if (hasMuteControl != ((getConnectionCapabilities() & CAPABILITY_MUTE)
- == CAPABILITY_MUTE)) {
- if (hasMuteControl) {
- capabilities |= CAPABILITY_MUTE;
- } else {
- capabilities &= ~CAPABILITY_MUTE;
- }
+ final boolean hasMuteControl = mCall.hasControl(android.companion.Telecom.MUTE)
+ || mCall.hasControl(android.companion.Telecom.UNMUTE);
+ if (hasMuteControl) {
+ capabilities |= CAPABILITY_MUTE;
+ } else {
+ capabilities &= ~CAPABILITY_MUTE;
}
mAudioManager.setMicrophoneMute(
mCall.hasControl(android.companion.Telecom.UNMUTE));
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
index b3cf772..d8621cb 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncData.java
@@ -20,7 +20,6 @@
import android.companion.ContextSyncMessage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telecom.PhoneAccountHandle;
import java.util.ArrayList;
import java.util.Collection;
@@ -189,7 +188,6 @@
private String mCallerId;
private byte[] mAppIcon;
private CallFacilitator mFacilitator;
- private PhoneAccountHandle mPhoneAccountHandle;
private int mStatus;
private final Set<Integer> mControls = new HashSet<>();
@@ -200,9 +198,6 @@
call.setAppIcon(parcel.readBlob());
call.setFacilitator(parcel.readParcelable(CallFacilitator.class.getClassLoader(),
CallFacilitator.class));
- call.setPhoneAccountHandle(
- parcel.readParcelable(PhoneAccountHandle.class.getClassLoader(),
- android.telecom.PhoneAccountHandle.class));
call.setStatus(parcel.readInt());
final int numberOfControls = parcel.readInt();
for (int i = 0; i < numberOfControls; i++) {
@@ -217,7 +212,6 @@
parcel.writeString(mCallerId);
parcel.writeBlob(mAppIcon);
parcel.writeParcelable(mFacilitator, parcelableFlags);
- parcel.writeParcelable(mPhoneAccountHandle, parcelableFlags);
parcel.writeInt(mStatus);
parcel.writeInt(mControls.size());
for (int control : mControls) {
@@ -241,10 +235,6 @@
mFacilitator = facilitator;
}
- void setPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
- mPhoneAccountHandle = phoneAccountHandle;
- }
-
void setStatus(int status) {
mStatus = status;
}
@@ -253,6 +243,11 @@
mControls.add(control);
}
+ void setControls(Set<Integer> controls) {
+ mControls.clear();
+ mControls.addAll(controls);
+ }
+
String getId() {
return mId;
}
@@ -269,10 +264,6 @@
return mFacilitator;
}
- PhoneAccountHandle getPhoneAccountHandle() {
- return mPhoneAccountHandle;
- }
-
int getStatus() {
return mStatus;
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
index 1f5e168..b46d5d3 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -79,16 +79,15 @@
int callControlAction) {
final CrossDeviceCall crossDeviceCall = getCallForId(crossDeviceCallId,
mCurrentCalls.values());
+ if (crossDeviceCall == null) {
+ return;
+ }
switch (callControlAction) {
case android.companion.Telecom.ACCEPT:
- if (crossDeviceCall != null) {
- crossDeviceCall.doAccept();
- }
+ crossDeviceCall.doAccept();
break;
case android.companion.Telecom.REJECT:
- if (crossDeviceCall != null) {
- crossDeviceCall.doReject();
- }
+ crossDeviceCall.doReject();
break;
case android.companion.Telecom.SILENCE:
doSilence();
@@ -100,19 +99,13 @@
doUnmute();
break;
case android.companion.Telecom.END:
- if (crossDeviceCall != null) {
- crossDeviceCall.doEnd();
- }
+ crossDeviceCall.doEnd();
break;
case android.companion.Telecom.PUT_ON_HOLD:
- if (crossDeviceCall != null) {
- crossDeviceCall.doPutOnHold();
- }
+ crossDeviceCall.doPutOnHold();
break;
case android.companion.Telecom.TAKE_OFF_HOLD:
- if (crossDeviceCall != null) {
- crossDeviceCall.doTakeOffHold();
- }
+ crossDeviceCall.doTakeOffHold();
break;
default:
}
@@ -148,7 +141,8 @@
super.onCreate();
if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)) {
mCdmsi = LocalServices.getService(CompanionDeviceManagerServiceInternal.class);
- mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback);
+ mCdmsi.registerCallMetadataSyncCallback(mCrossDeviceSyncControllerCallback,
+ CrossDeviceSyncControllerCallback.TYPE_IN_CALL_SERVICE);
}
}
@@ -156,7 +150,7 @@
if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
&& mNumberOfActiveSyncAssociations > 0) {
mCurrentCalls.putAll(getCalls().stream().collect(Collectors.toMap(call -> call,
- call -> new CrossDeviceCall(getPackageManager(), call, getCallAudioState()))));
+ call -> new CrossDeviceCall(this, call, getCallAudioState()))));
mCurrentCalls.keySet().forEach(call -> call.registerCallback(mTelecomCallback,
getMainThreadHandler()));
sync(getUserId());
@@ -182,7 +176,7 @@
if (CompanionDeviceConfig.isEnabled(CompanionDeviceConfig.ENABLE_CONTEXT_SYNC_TELECOM)
&& mNumberOfActiveSyncAssociations > 0) {
mCurrentCalls.put(call,
- new CrossDeviceCall(getPackageManager(), call, getCallAudioState()));
+ new CrossDeviceCall(this, call, getCallAudioState()));
call.registerCallback(mTelecomCallback);
sync(getUserId());
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
index de7bf40..fec6923 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceCall.java
@@ -17,10 +17,15 @@
package com.android.server.companion.datatransfer.contextsync;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.telecom.Call;
import android.telecom.CallAudioState;
+import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Slog;
@@ -35,40 +40,51 @@
private static final String TAG = "CrossDeviceCall";
- public static final String EXTRA_CALL_ID =
- "com.android.companion.datatransfer.contextsync.extra.CALL_ID";
-
private final String mId;
- private Call mCall;
+ private final Call mCall;
@VisibleForTesting boolean mIsEnterprise;
- @VisibleForTesting boolean mIsOtt;
private final String mCallingAppPackageName;
private String mCallingAppName;
private byte[] mCallingAppIcon;
private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
private int mStatus = android.companion.Telecom.Call.UNKNOWN_STATUS;
private String mContactDisplayName;
+ private Uri mHandle;
+ private int mHandlePresentation;
private boolean mIsMuted;
private final Set<Integer> mControls = new HashSet<>();
+ private final boolean mIsCallPlacedByContextSync;
- public CrossDeviceCall(PackageManager packageManager, @NonNull Call call,
+ public CrossDeviceCall(Context context, @NonNull Call call,
CallAudioState callAudioState) {
- this(packageManager, call.getDetails(), callAudioState);
- mCall = call;
- call.putExtra(EXTRA_CALL_ID, mId);
+ this(context, call, call.getDetails(), callAudioState);
}
- CrossDeviceCall(PackageManager packageManager, Call.Details callDetails,
+ CrossDeviceCall(Context context, Call.Details callDetails,
CallAudioState callAudioState) {
+ this(context, /* call= */ null, callDetails, callAudioState);
+ }
+
+ private CrossDeviceCall(Context context, @Nullable Call call,
+ Call.Details callDetails, CallAudioState callAudioState) {
+ mCall = call;
final String predefinedId = callDetails.getIntentExtras() != null
- ? callDetails.getIntentExtras().getString(EXTRA_CALL_ID) : null;
- mId = predefinedId != null ? predefinedId : UUID.randomUUID().toString();
+ ? callDetails.getIntentExtras().getString(CrossDeviceSyncController.EXTRA_CALL_ID)
+ : null;
+ final String generatedId = UUID.randomUUID().toString();
+ mId = predefinedId != null ? (generatedId + predefinedId) : generatedId;
+ if (call != null) {
+ call.putExtra(CrossDeviceSyncController.EXTRA_CALL_ID, mId);
+ }
+ mIsCallPlacedByContextSync =
+ new ComponentName(context, CallMetadataSyncConnectionService.class)
+ .equals(callDetails.getAccountHandle().getComponentName());
mCallingAppPackageName =
callDetails.getAccountHandle().getComponentName().getPackageName();
- mIsOtt = (callDetails.getCallCapabilities() & Call.Details.PROPERTY_SELF_MANAGED)
- == Call.Details.PROPERTY_SELF_MANAGED;
mIsEnterprise = (callDetails.getCallProperties() & Call.Details.PROPERTY_ENTERPRISE_CALL)
== Call.Details.PROPERTY_ENTERPRISE_CALL;
+ final PackageManager packageManager = context.getPackageManager();
try {
final ApplicationInfo applicationInfo = packageManager
.getApplicationInfo(mCallingAppPackageName,
@@ -108,7 +124,10 @@
@VisibleForTesting
void updateCallDetails(Call.Details callDetails) {
mCallerDisplayName = callDetails.getCallerDisplayName();
+ mCallerDisplayNamePresentation = callDetails.getCallerDisplayNamePresentation();
mContactDisplayName = callDetails.getContactDisplayName();
+ mHandle = callDetails.getHandle();
+ mHandlePresentation = callDetails.getHandlePresentation();
mStatus = convertStateToStatus(callDetails.getState());
mControls.clear();
if (mStatus == android.companion.Telecom.Call.RINGING
@@ -145,7 +164,14 @@
return android.companion.Telecom.Call.ONGOING;
case Call.STATE_RINGING:
return android.companion.Telecom.Call.RINGING;
+ case Call.STATE_AUDIO_PROCESSING:
+ return android.companion.Telecom.Call.AUDIO_PROCESSING;
+ case Call.STATE_SIMULATED_RINGING:
+ return android.companion.Telecom.Call.RINGING_SIMULATED;
+ case Call.STATE_DISCONNECTED:
+ return android.companion.Telecom.Call.DISCONNECTED;
default:
+ Slog.e(TAG, "Couldn't resolve state to status: " + callState);
return android.companion.Telecom.Call.UNKNOWN_STATUS;
}
}
@@ -163,6 +189,12 @@
case android.companion.Telecom.Call.RINGING:
case android.companion.Telecom.Call.RINGING_SILENCED:
return Call.STATE_RINGING;
+ case android.companion.Telecom.Call.AUDIO_PROCESSING:
+ return Call.STATE_AUDIO_PROCESSING;
+ case android.companion.Telecom.Call.RINGING_SIMULATED:
+ return Call.STATE_SIMULATED_RINGING;
+ case android.companion.Telecom.Call.DISCONNECTED:
+ return Call.STATE_DISCONNECTED;
case android.companion.Telecom.Call.UNKNOWN_STATUS:
default:
return Call.STATE_NEW;
@@ -195,10 +227,23 @@
* @param isAdminBlocked whether there is an admin that has blocked contacts over Bluetooth
*/
public String getReadableCallerId(boolean isAdminBlocked) {
- if (mIsOtt) {
+ if (mIsEnterprise && isAdminBlocked) {
+ // Cannot use any contact information.
+ return getNonContactString();
+ }
+ return mContactDisplayName != null ? mContactDisplayName : getNonContactString();
+ }
+
+ private String getNonContactString() {
+ if (mCallerDisplayName != null
+ && mCallerDisplayNamePresentation == TelecomManager.PRESENTATION_ALLOWED) {
return mCallerDisplayName;
}
- return mIsEnterprise && isAdminBlocked ? mCallerDisplayName : mContactDisplayName;
+ if (mHandle != null && mHandle.getSchemeSpecificPart() != null
+ && mHandlePresentation == TelecomManager.PRESENTATION_ALLOWED) {
+ return mHandle.getSchemeSpecificPart();
+ }
+ return null;
}
public int getStatus() {
@@ -209,6 +254,10 @@
return mControls;
}
+ public boolean isCallPlacedByContextSync() {
+ return mIsCallPlacedByContextSync;
+ }
+
void doAccept() {
mCall.answer(VideoProfile.STATE_AUDIO_ONLY);
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
index 8c6ff86..bf82f3f 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncController.java
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceManager;
import android.companion.ContextSyncMessage;
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
@@ -44,8 +45,10 @@
import com.android.server.companion.transport.CompanionTransportManager;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -54,6 +57,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.stream.Collectors;
/**
* Monitors connections and sending / receiving of synced data.
@@ -62,6 +66,13 @@
private static final String TAG = "CrossDeviceSyncController";
+ public static final String EXTRA_CALL_ID =
+ "com.android.companion.datatransfer.contextsync.extra.CALL_ID";
+ static final String EXTRA_FACILITATOR_ICON =
+ "com.android.companion.datatransfer.contextsync.extra.FACILITATOR_ICON";
+ static final String EXTRA_IS_REMOTE_ORIGIN =
+ "com.android.companion.datatransfer.contextsync.extra.IS_REMOTE_ORIGIN";
+
static final String EXTRA_ASSOCIATION_ID =
"com.android.server.companion.datatransfer.contextsync.extra.ASSOCIATION_ID";
static final String EXTRA_CALL =
@@ -78,11 +89,13 @@
private final Context mContext;
private final CompanionTransportManager mCompanionTransportManager;
private final PhoneAccountManager mPhoneAccountManager;
+ private final CallManager mCallManager;
private final List<AssociationInfo> mConnectedAssociations = new ArrayList<>();
private final Set<Integer> mBlocklist = new HashSet<>();
private final List<CallMetadataSyncData.CallFacilitator> mCallFacilitators = new ArrayList<>();
- private CrossDeviceSyncControllerCallback mCrossDeviceSyncControllerCallback;
+ private WeakReference<CrossDeviceSyncControllerCallback> mInCallServiceCallbackRef;
+ private WeakReference<CrossDeviceSyncControllerCallback> mConnectionServiceCallbackRef;
public CrossDeviceSyncController(Context context,
CompanionTransportManager companionTransportManager) {
@@ -104,25 +117,77 @@
mConnectedAssociations);
mConnectedAssociations.clear();
mConnectedAssociations.addAll(newAssociations);
- if (mCrossDeviceSyncControllerCallback == null) {
- Slog.w(TAG, "No callback to report transports changed");
- return;
- }
for (AssociationInfo associationInfo : newAssociations) {
- if (!existingAssociations.contains(associationInfo)
- && !isAssociationBlocked(associationInfo.getId())) {
- mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
- associationInfo.getUserId(), /* added= */ true);
- mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+ if (!existingAssociations.contains(associationInfo)) {
+ // New association.
+ if (!isAssociationBlocked(associationInfo)) {
+ final CrossDeviceSyncControllerCallback callback =
+ mInCallServiceCallbackRef != null
+ ? mInCallServiceCallbackRef.get() : null;
+ if (callback != null) {
+ callback.updateNumberOfActiveSyncAssociations(
+ associationInfo.getUserId(), /* added= */ true);
+ callback.requestCrossDeviceSync(associationInfo);
+ } else {
+ Slog.w(TAG, "No callback to report new transport");
+ syncMessageToDevice(associationInfo.getId(),
+ createFacilitatorMessage());
+ }
+ } else {
+ mBlocklist.add(associationInfo.getId());
+ Slog.i(TAG, "New association was blocked from context syncing");
+ }
}
}
for (AssociationInfo associationInfo : existingAssociations) {
if (!newAssociations.contains(associationInfo)) {
- if (isAssociationBlocked(associationInfo.getId())) {
- mBlocklist.remove(associationInfo.getId());
- } else {
- mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
- associationInfo.getUserId(), /* added= */ false);
+ // Removed association!
+ mBlocklist.remove(associationInfo.getId());
+ if (!isAssociationBlockedLocal(associationInfo.getId())) {
+ final CrossDeviceSyncControllerCallback callback =
+ mInCallServiceCallbackRef != null
+ ? mInCallServiceCallbackRef.get() : null;
+ if (callback != null) {
+ callback.updateNumberOfActiveSyncAssociations(
+ associationInfo.getUserId(), /* added= */ false);
+ } else {
+ Slog.w(TAG, "No callback to report removed transport");
+ }
+ }
+ } else {
+ // Stable association!
+ final boolean systemBlocked = isAssociationBlocked(associationInfo);
+ if (isAssociationBlockedLocal(associationInfo.getId()) != systemBlocked) {
+ // Block state has changed.
+ final CrossDeviceSyncControllerCallback callback =
+ mInCallServiceCallbackRef != null
+ ? mInCallServiceCallbackRef.get() : null;
+ if (!systemBlocked) {
+ Slog.i(TAG, "Unblocking existing association for context sync");
+ mBlocklist.remove(associationInfo.getId());
+ if (callback != null) {
+ callback.updateNumberOfActiveSyncAssociations(
+ associationInfo.getUserId(), /* added= */ true);
+ callback.requestCrossDeviceSync(associationInfo);
+ } else {
+ Slog.w(TAG, "No callback to report changed transport");
+ syncMessageToDevice(associationInfo.getId(),
+ createFacilitatorMessage());
+ }
+ } else {
+ Slog.i(TAG, "Blocking existing association for context sync");
+ mBlocklist.add(associationInfo.getId());
+ if (callback != null) {
+ callback.updateNumberOfActiveSyncAssociations(
+ associationInfo.getUserId(), /* added= */ false);
+ } else {
+ Slog.w(TAG, "No callback to report changed transport");
+ }
+ // Send empty message to device to clear its data (otherwise it
+ // will get stale)
+ syncMessageToDevice(associationInfo.getId(),
+ createEmptyMessage());
+ }
}
}
}
@@ -132,18 +197,48 @@
new IOnMessageReceivedListener.Stub() {
@Override
public void onMessageReceived(int associationId, byte[] data) {
+ if (isAssociationBlockedLocal(associationId)) {
+ return;
+ }
final CallMetadataSyncData processedData = processTelecomDataFromSync(data);
mPhoneAccountManager.updateFacilitators(associationId, processedData);
- processCallCreateRequests(associationId, processedData);
- if (mCrossDeviceSyncControllerCallback == null) {
+ mCallManager.updateCalls(associationId, processedData);
+ processCallCreateRequests(processedData);
+ if (mInCallServiceCallbackRef == null
+ && mConnectionServiceCallbackRef == null) {
Slog.w(TAG, "No callback to process context sync message");
return;
}
- mCrossDeviceSyncControllerCallback.processContextSyncMessage(associationId,
- processedData);
+ final CrossDeviceSyncControllerCallback inCallServiceCallback =
+ mInCallServiceCallbackRef != null ? mInCallServiceCallbackRef.get()
+ : null;
+ if (inCallServiceCallback != null) {
+ inCallServiceCallback.processContextSyncMessage(associationId,
+ processedData);
+ } else {
+ // This is dead; get rid of it lazily
+ mInCallServiceCallbackRef = null;
+ }
+
+ final CrossDeviceSyncControllerCallback connectionServiceCallback =
+ mConnectionServiceCallbackRef != null
+ ? mConnectionServiceCallbackRef.get() : null;
+ if (connectionServiceCallback != null) {
+ connectionServiceCallback.processContextSyncMessage(associationId,
+ processedData);
+ } else {
+ // This is dead; get rid of it lazily
+ mConnectionServiceCallbackRef = null;
+ }
}
});
mPhoneAccountManager = new PhoneAccountManager(mContext);
+ mCallManager = new CallManager(mContext, mPhoneAccountManager);
+ }
+
+ private static boolean isAssociationBlocked(AssociationInfo info) {
+ return (info.getSystemDataSyncFlags() & CompanionDeviceManager.FLAG_CALL_METADATA)
+ != CompanionDeviceManager.FLAG_CALL_METADATA;
}
/** Invoke set-up tasks that happen when boot is completed. */
@@ -155,7 +250,7 @@
mPhoneAccountManager.onBootCompleted();
final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
- if (telecomManager.getCallCapablePhoneAccounts().size() != 0) {
+ if (telecomManager != null && telecomManager.getCallCapablePhoneAccounts().size() != 0) {
final PhoneAccountHandle defaultOutgoingTelAccountHandle =
telecomManager.getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
if (defaultOutgoingTelAccountHandle != null) {
@@ -171,8 +266,7 @@
}
}
- private void processCallCreateRequests(int associationId,
- CallMetadataSyncData callMetadataSyncData) {
+ private void processCallCreateRequests(CallMetadataSyncData callMetadataSyncData) {
final Iterator<CallMetadataSyncData.CallCreateRequest> iterator =
callMetadataSyncData.getCallCreateRequests().iterator();
while (iterator.hasNext()) {
@@ -184,7 +278,7 @@
final Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL,
request.getAddress().replaceAll("\\D+", ""), /* fragment= */ null);
final Bundle extras = new Bundle();
- extras.putString(CrossDeviceCall.EXTRA_CALL_ID, request.getId());
+ extras.putString(EXTRA_CALL_ID, request.getId());
final Bundle outerExtras = new Bundle();
outerExtras.putParcelable(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
mContext.getSystemService(TelecomManager.class).placeCall(uri, outerExtras);
@@ -196,39 +290,33 @@
}
}
- private boolean isAssociationBlocked(int associationId) {
+ /**
+ * This keeps track of "previous" state to calculate deltas. Use {@link #isAssociationBlocked}
+ * for all other use cases.
+ */
+ private boolean isAssociationBlockedLocal(int associationId) {
return mBlocklist.contains(associationId);
}
/** Registers the call metadata callback. */
- public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback) {
- mCrossDeviceSyncControllerCallback = callback;
- for (AssociationInfo associationInfo : mConnectedAssociations) {
- if (!isAssociationBlocked(associationInfo.getId())) {
- mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
- associationInfo.getUserId(), /* added= */ true);
- mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
+ public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback,
+ @CrossDeviceSyncControllerCallback.Type int type) {
+ if (type == CrossDeviceSyncControllerCallback.TYPE_IN_CALL_SERVICE) {
+ mInCallServiceCallbackRef = new WeakReference<>(callback);
+ for (AssociationInfo associationInfo : mConnectedAssociations) {
+ if (!isAssociationBlocked(associationInfo)) {
+ mBlocklist.remove(associationInfo.getId());
+ callback.updateNumberOfActiveSyncAssociations(associationInfo.getUserId(),
+ /* added= */ true);
+ callback.requestCrossDeviceSync(associationInfo);
+ } else {
+ mBlocklist.add(associationInfo.getId());
+ }
}
- }
- }
-
- /** Allow specific associated devices to enable / disable syncing. */
- public void setSyncEnabled(AssociationInfo associationInfo, boolean enabled) {
- if (enabled) {
- if (isAssociationBlocked(associationInfo.getId())) {
- mBlocklist.remove(associationInfo.getId());
- mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
- associationInfo.getUserId(), /* added= */ true);
- mCrossDeviceSyncControllerCallback.requestCrossDeviceSync(associationInfo);
- }
+ } else if (type == CrossDeviceSyncControllerCallback.TYPE_CONNECTION_SERVICE) {
+ mConnectionServiceCallbackRef = new WeakReference<>(callback);
} else {
- if (!isAssociationBlocked(associationInfo.getId())) {
- mBlocklist.add(associationInfo.getId());
- mCrossDeviceSyncControllerCallback.updateNumberOfActiveSyncAssociations(
- associationInfo.getUserId(), /* added= */ false);
- // Send empty message to device to clear its data (otherwise it will get stale)
- syncMessageToDevice(associationInfo.getId(), createEmptyMessage());
- }
+ Slog.e(TAG, "Cannot register callback of unknown type: " + type);
}
}
@@ -246,8 +334,7 @@
public void syncToAllDevicesForUserId(int userId, Collection<CrossDeviceCall> calls) {
final Set<Integer> associationIds = new HashSet<>();
for (AssociationInfo associationInfo : mConnectedAssociations) {
- if (associationInfo.getUserId() == userId && !isAssociationBlocked(
- associationInfo.getId())) {
+ if (associationInfo.getUserId() == userId && !isAssociationBlocked(associationInfo)) {
associationIds.add(associationInfo.getId());
}
}
@@ -269,7 +356,7 @@
*/
public void syncToSingleDevice(AssociationInfo associationInfo,
Collection<CrossDeviceCall> calls) {
- if (isAssociationBlocked(associationInfo.getId())) {
+ if (isAssociationBlocked(associationInfo)) {
Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
return;
}
@@ -286,7 +373,7 @@
* @param message The message to sync.
*/
public void syncMessageToDevice(int associationId, byte[] message) {
- if (isAssociationBlocked(associationId)) {
+ if (isAssociationBlockedLocal(associationId)) {
Slog.e(TAG, "Cannot sync to requested device; connection is blocked");
return;
}
@@ -491,6 +578,10 @@
pos.write(ContextSyncMessage.VERSION, CURRENT_VERSION);
final long telecomToken = pos.start(ContextSyncMessage.TELECOM);
for (CrossDeviceCall call : calls) {
+ if (call.isCallPlacedByContextSync()) {
+ // Do not sync any calls which our "ours" as that would be duplicative.
+ continue;
+ }
final long callsToken = pos.start(Telecom.CALLS);
pos.write(Telecom.Call.ID, call.getId());
final long originToken = pos.start(Telecom.Call.ORIGIN);
@@ -559,6 +650,50 @@
return pos.getBytes();
}
+ /** Create a facilitator-only message, used before any calls are available as a call intake. */
+ private byte[] createFacilitatorMessage() {
+ return createCallUpdateMessage(Collections.emptyList(), -1);
+ }
+
+ @VisibleForTesting
+ static class CallManager {
+
+ @VisibleForTesting final Map<Integer, Set<String>> mCallIds = new HashMap<>();
+ private final TelecomManager mTelecomManager;
+ private final PhoneAccountManager mPhoneAccountManager;
+
+ CallManager(Context context, PhoneAccountManager phoneAccountManager) {
+ mTelecomManager = context.getSystemService(TelecomManager.class);
+ mPhoneAccountManager = phoneAccountManager;
+ }
+
+ /** Add any new calls to Telecom. The ConnectionService will handle everything else. */
+ void updateCalls(int associationId, CallMetadataSyncData data) {
+ final Set<String> oldCallIds = mCallIds.getOrDefault(associationId, new HashSet<>());
+ final Set<String> newCallIds = data.getCalls().stream().map(
+ CallMetadataSyncData.Call::getId).collect(Collectors.toSet());
+ if (oldCallIds.equals(newCallIds)) {
+ return;
+ }
+
+ for (CallMetadataSyncData.Call currentCall : data.getCalls()) {
+ if (!oldCallIds.contains(currentCall.getId())
+ && currentCall.getFacilitator() != null) {
+ final Bundle extras = new Bundle();
+ extras.putInt(EXTRA_ASSOCIATION_ID, associationId);
+ extras.putBoolean(EXTRA_IS_REMOTE_ORIGIN, true);
+ extras.putParcelable(EXTRA_CALL, currentCall);
+ extras.putString(EXTRA_CALL_ID, currentCall.getId());
+ extras.putByteArray(EXTRA_FACILITATOR_ICON, currentCall.getAppIcon());
+ final PhoneAccountHandle handle = mPhoneAccountManager.getPhoneAccountHandle(
+ associationId, currentCall.getFacilitator().getIdentifier());
+ mTelecomManager.addNewIncomingCall(handle, extras);
+ }
+ }
+ mCallIds.put(associationId, newCallIds);
+ }
+ }
+
static class PhoneAccountManager {
private final Map<PhoneAccountHandleIdentifier, PhoneAccountHandle> mPhoneAccountHandles =
new HashMap<>();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
index 31e10a8..8a0ba27 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerCallback.java
@@ -16,11 +16,25 @@
package com.android.server.companion.datatransfer.contextsync;
+import android.annotation.IntDef;
import android.companion.AssociationInfo;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/** Callback for call metadata syncing. */
public abstract class CrossDeviceSyncControllerCallback {
+ static final int TYPE_CONNECTION_SERVICE = 1;
+ static final int TYPE_IN_CALL_SERVICE = 2;
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_CONNECTION_SERVICE,
+ TYPE_IN_CALL_SERVICE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {
+ }
+
void processContextSyncMessage(int associationId, CallMetadataSyncData callMetadataSyncData) {}
void requestCrossDeviceSync(AssociationInfo associationInfo) {}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 392b5df..4562b8f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -262,8 +262,7 @@
private static final boolean DEBUG_DELAYED_SERVICE = DEBUG_SERVICE;
private static final boolean DEBUG_DELAYED_STARTS = DEBUG_DELAYED_SERVICE;
- // STOPSHIP(b/260012573) turn it off.
- private static final boolean DEBUG_SHORT_SERVICE = true; // DEBUG_SERVICE;
+ private static final boolean DEBUG_SHORT_SERVICE = DEBUG_SERVICE;
private static final boolean LOG_SERVICE_START_STOP = DEBUG_SERVICE;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c78229b..eb7fa10 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -342,16 +342,20 @@
return supportedStates;
}
+ /**
+ * Returns the current {@link DeviceStateInfo} of the device. If there has been no base state
+ * or committed state provided, {@link DeviceStateManager#INVALID_DEVICE_STATE} will be returned
+ * respectively. The supported states will always be included.
+ *
+ */
+ @GuardedBy("mLock")
@NonNull
private DeviceStateInfo getDeviceStateInfoLocked() {
- if (!mBaseState.isPresent() || !mCommittedState.isPresent()) {
- throw new IllegalStateException("Trying to get the current DeviceStateInfo before the"
- + " initial state has been committed.");
- }
-
final int[] supportedStates = getSupportedStateIdentifiersLocked();
- final int baseState = mBaseState.get().getIdentifier();
- final int currentState = mCommittedState.get().getIdentifier();
+ final int baseState =
+ mBaseState.isPresent() ? mBaseState.get().getIdentifier() : INVALID_DEVICE_STATE;
+ final int currentState = mCommittedState.isPresent() ? mCommittedState.get().getIdentifier()
+ : INVALID_DEVICE_STATE;
return new DeviceStateInfo(supportedStates, baseState, currentState);
}
@@ -715,6 +719,9 @@
}
mProcessRecords.put(pid, record);
+ // Callback clients should not be notified of invalid device states, so calls to
+ // #getDeviceStateInfoLocked should be gated on checks if a committed state is present
+ // before getting the device state info.
DeviceStateInfo currentInfo = mCommittedState.isPresent()
? getDeviceStateInfoLocked() : null;
if (currentInfo != null) {
@@ -1128,6 +1135,7 @@
/** Implementation of {@link IDeviceStateManager} published as a binder service. */
private final class BinderService extends IDeviceStateManager.Stub {
+
@Override // Binder call
public DeviceStateInfo getDeviceStateInfo() {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 22b6a53..e8c65ef 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -316,7 +316,9 @@
}
/**
- * Notify the BrightnessTracker that the user has changed the brightness of the display.
+ * Notify the BrightnessTracker that the brightness of the display has changed.
+ * We pass both the user change and system changes, so that we know the starting point
+ * of the next user interaction. Only user interactions are then sent as BrightnessChangeEvents.
*/
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean wasShortTermModelActive,
@@ -352,10 +354,8 @@
// Not currently gathering brightness change information
return;
}
-
float previousBrightness = mLastBrightness;
mLastBrightness = brightness;
-
if (!userInitiated) {
// We want to record what current brightness is so that we know what the user
// changed it from, but if it wasn't user initiated then we don't want to record it
@@ -429,7 +429,7 @@
BrightnessChangeEvent event = builder.build();
if (DEBUG) {
- Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
+ Slog.d(TAG, "Event: " + event.toString());
}
synchronized (mEventsLock) {
mEventsDirty = true;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 0861cb5..9d31572 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1908,21 +1908,8 @@
}
}
- // Report brightness to brightnesstracker:
- // If brightness is not temporary (ie the slider has been released)
- // AND if we are not in idle screen brightness mode.
- if (!brightnessIsTemporary
- && (mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode())) {
- if (userInitiatedChange && (mAutomaticBrightnessController == null
- || !mAutomaticBrightnessController.hasValidAmbientLux())) {
- // If we don't have a valid lux reading we can't report a valid
- // slider event so notify as if the system changed the brightness.
- userInitiatedChange = false;
- }
- notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- wasShortTermModelActive);
- }
+ notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+ wasShortTermModelActive, autoBrightnessEnabled, brightnessIsTemporary);
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
@@ -2758,22 +2745,43 @@
}
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
- boolean wasShortTermModelActive) {
+ boolean wasShortTermModelActive, boolean autobrightnessEnabled,
+ boolean brightnessIsTemporary) {
final float brightnessInNits = convertToAdjustedNits(brightness);
- if (mUseAutoBrightness && brightnessInNits >= 0.0f
- && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
- // We only want to track changes on devices that can actually map the display backlight
- // values into a physical brightness unit since the value provided by the API is in
- // nits and not using the arbitrary backlight units.
- final float powerFactor = mPowerRequest.lowPowerMode
- ? mPowerRequest.screenLowPowerBrightnessFactor
- : 1.0f;
- mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
- powerFactor, wasShortTermModelActive,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
- mAutomaticBrightnessController.getLastSensorValues(),
- mAutomaticBrightnessController.getLastSensorTimestamps());
+
+ // Don't report brightness to brightnessTracker:
+ // If brightness is temporary (ie the slider has not been released)
+ // or if we are in idle screen brightness mode.
+ // or display is not on
+ // or we shouldn't be using autobrightness
+ // or the nits is invalid.
+ if (brightnessIsTemporary
+ || mAutomaticBrightnessController == null
+ || mAutomaticBrightnessController.isInIdleMode()
+ || !autobrightnessEnabled
+ || mBrightnessTracker == null
+ || !mUseAutoBrightness
+ || brightnessInNits < 0.0f) {
+ return;
}
+
+ if (userInitiated && !mAutomaticBrightnessController.hasValidAmbientLux()) {
+ // If we don't have a valid lux reading we can't report a valid
+ // slider event so notify as if the system changed the brightness.
+ userInitiated = false;
+ }
+
+ // We only want to track changes on devices that can actually map the display backlight
+ // values into a physical brightness unit since the value provided by the API is in
+ // nits and not using the arbitrary backlight units.
+ final float powerFactor = mPowerRequest.lowPowerMode
+ ? mPowerRequest.screenLowPowerBrightnessFactor
+ : 1.0f;
+ mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
+ powerFactor, wasShortTermModelActive,
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
private float convertToNits(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3b3d5da..1674141 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1454,7 +1454,7 @@
// Skip the animation when the screen is off or suspended.
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
- (mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
+ (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY)
|| mAutomaticBrightnessStrategy
.isTemporaryAutoBrightnessAdjustmentApplied();
if (!mPendingScreenOff) {
@@ -1539,21 +1539,9 @@
}
}
- // Report brightness to brightnesstracker:
- // If brightness is not temporary (ie the slider has been released)
- // AND if we are not in idle screen brightness mode.
- if (!brightnessIsTemporary
- && (mAutomaticBrightnessController != null
- && !mAutomaticBrightnessController.isInIdleMode())) {
- if (userInitiatedChange && (mAutomaticBrightnessController == null
- || !mAutomaticBrightnessController.hasValidAmbientLux())) {
- // If we don't have a valid lux reading we can't report a valid
- // slider event so notify as if the system changed the brightness.
- userInitiatedChange = false;
- }
- notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
- wasShortTermModelActive);
- }
+ notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+ wasShortTermModelActive, mAutomaticBrightnessStrategy.isAutoBrightnessEnabled(),
+ brightnessIsTemporary);
// We save the brightness info *after* the brightness setting has been changed and
// adjustments made so that the brightness info reflects the latest value.
@@ -2171,11 +2159,6 @@
}, mClock.uptimeMillis());
}
- private float getAutoBrightnessAdjustmentSetting() {
- final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
- return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
- }
@Override
public float getScreenBrightnessSetting() {
@@ -2215,23 +2198,45 @@
}
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
- boolean wasShortTermModelActive) {
+ boolean wasShortTermModelActive, boolean autobrightnessEnabled,
+ boolean brightnessIsTemporary) {
+
final float brightnessInNits =
mDisplayBrightnessController.convertToAdjustedNits(brightness);
- if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f
- && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
- // We only want to track changes on devices that can actually map the display backlight
- // values into a physical brightness unit since the value provided by the API is in
- // nits and not using the arbitrary backlight units.
- final float powerFactor = mPowerRequest.lowPowerMode
- ? mPowerRequest.screenLowPowerBrightnessFactor
- : 1.0f;
- mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
- powerFactor, wasShortTermModelActive,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
- mAutomaticBrightnessController.getLastSensorValues(),
- mAutomaticBrightnessController.getLastSensorTimestamps());
+ // Don't report brightness to brightnessTracker:
+ // If brightness is temporary (ie the slider has not been released)
+ // or if we are in idle screen brightness mode.
+ // or display is not on
+ // or we shouldn't be using autobrightness
+ // or the nits is invalid.
+ if (brightnessIsTemporary
+ || mAutomaticBrightnessController == null
+ || mAutomaticBrightnessController.isInIdleMode()
+ || !autobrightnessEnabled
+ || mBrightnessTracker == null
+ || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ || brightnessInNits < 0.0f) {
+ return;
}
+
+ if (userInitiated && (mAutomaticBrightnessController == null
+ || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+ // If we don't have a valid lux reading we can't report a valid
+ // slider event so notify as if the system changed the brightness.
+ userInitiated = false;
+ }
+
+ // We only want to track changes on devices that can actually map the display backlight
+ // values into a physical brightness unit since the value provided by the API is in
+ // nits and not using the arbitrary backlight units.
+ final float powerFactor = mPowerRequest.lowPowerMode
+ ? mPowerRequest.screenLowPowerBrightnessFactor
+ : 1.0f;
+ mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
+ powerFactor, wasShortTermModelActive,
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
@Override
@@ -2426,9 +2431,6 @@
}
}
- private static float clampAutoBrightnessAdjustment(float value) {
- return MathUtils.constrain(value, -1.0f, 1.0f);
- }
private void noteScreenState(int screenState) {
// Log screen state change with display id
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
index 169cc4a..3fae224 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessUtils.java
@@ -42,6 +42,13 @@
}
/**
+ * Clamps the brightness value in the maximum and the minimum brightness adjustment range
+ */
+ public static float clampBrightnessAdjustment(float value) {
+ return MathUtils.constrain(value, -1.0f, 1.0f);
+ }
+
+ /**
* A utility to construct the DisplayBrightnessState
*/
public static DisplayBrightnessState constructDisplayBrightnessState(
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 95cbf98..0e885dc 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -202,7 +202,7 @@
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
- : BrightnessUtils.clampAbsoluteBrightness(adj);
+ : BrightnessUtils.clampBrightnessAdjustment(adj);
if (userSwitch) {
processPendingAutoBrightnessAdjustments();
}
@@ -402,6 +402,6 @@
private float getAutoBrightnessAdjustmentSetting() {
final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
- return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampAbsoluteBrightness(adj);
+ return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampBrightnessAdjustment(adj);
}
}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 182aa6f..93f6ff3 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -42,13 +42,10 @@
import java.util.Objects;
-
/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
private static final String TAG = "APDeviceRoutesController";
- private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
-
@NonNull
private final Context mContext;
@NonNull
@@ -182,10 +179,12 @@
synchronized (this) {
return new MediaRoute2Info.Builder(
- DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
- .setVolumeHandling(mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ MediaRoute2Info.ROUTE_ID_DEVICE,
+ mContext.getResources().getText(name).toString())
+ .setVolumeHandling(
+ mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolume(mDeviceVolume)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setType(type)
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index cac22a6..b79991e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -28,6 +28,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
@@ -75,8 +76,10 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -97,6 +100,17 @@
private static final String KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE =
"scanning_package_minimum_importance";
+ /**
+ * Contains the list of bluetooth permissions that are required to do system routing.
+ *
+ * <p>Alternatively, apps that hold {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} are
+ * also allowed to do system routing.
+ */
+ private static final String[] BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING =
+ new String[] {
+ Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN
+ };
+
private static int sPackageImportanceForScanning = DeviceConfig.getInt(
MEDIA_BETTER_TOGETHER_NAMESPACE,
/* name */ KEY_SCANNING_PACKAGE_MINIMUM_IMPORTANCE,
@@ -142,6 +156,7 @@
}
};
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
/* package */ MediaRouter2ServiceImpl(Context context) {
mContext = context;
mActivityManager = mContext.getSystemService(ActivityManager.class);
@@ -155,12 +170,28 @@
screenOnOffIntentFilter.addAction(ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
+ mContext.getPackageManager().addOnPermissionsChangeListener(this::onPermissionsChanged);
DeviceConfig.addOnPropertiesChangedListener(MEDIA_BETTER_TOGETHER_NAMESPACE,
ActivityThread.currentApplication().getMainExecutor(),
this::onDeviceConfigChange);
}
+ /**
+ * Called when there's a change in the permissions of an app.
+ *
+ * @param uid The uid of the app whose permissions changed.
+ */
+ private void onPermissionsChanged(int uid) {
+ synchronized (mLock) {
+ Optional<RouterRecord> affectedRouter =
+ mAllRouterRecords.values().stream().filter(it -> it.mUid == uid).findFirst();
+ if (affectedRouter.isPresent()) {
+ affectedRouter.get().maybeUpdateSystemRoutingPermissionLocked();
+ }
+ }
+ }
+
// Start of methods that implement MediaRouter2 operations.
@NonNull
@@ -1511,6 +1542,7 @@
public final int mPid;
public final boolean mHasConfigureWifiDisplayPermission;
public final boolean mHasModifyAudioRoutingPermission;
+ public final AtomicBoolean mHasBluetoothRoutingPermission;
public final int mRouterId;
public RouteDiscoveryPreference mDiscoveryPreference;
@@ -1528,15 +1560,47 @@
mPid = pid;
mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
+ mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
mRouterId = mNextRouterOrManagerId.getAndIncrement();
}
+ private boolean fetchBluetoothPermission() {
+ boolean hasBluetoothRoutingPermission = true;
+ for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
+ hasBluetoothRoutingPermission &=
+ mContext.checkPermission(permission, mPid, mUid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return hasBluetoothRoutingPermission;
+ }
+
/**
* Returns whether the corresponding router has permission to query and control system
* routes.
*/
public boolean hasSystemRoutingPermission() {
- return mHasModifyAudioRoutingPermission;
+ return mHasModifyAudioRoutingPermission || mHasBluetoothRoutingPermission.get();
+ }
+
+ public void maybeUpdateSystemRoutingPermissionLocked() {
+ boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
+ mHasBluetoothRoutingPermission.set(fetchBluetoothPermission());
+ boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
+ if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
+ Map<String, MediaRoute2Info> routesToReport =
+ newSystemRoutingPermissionValue
+ ? mUserRecord.mHandler.mLastNotifiedRoutesToPrivilegedRouters
+ : mUserRecord.mHandler.mLastNotifiedRoutesToNonPrivilegedRouters;
+ notifyRoutesUpdated(routesToReport.values().stream().toList());
+
+ List<RoutingSessionInfo> sessionInfos =
+ mUserRecord.mHandler.mSystemProvider.getSessionInfos();
+ RoutingSessionInfo systemSessionToReport =
+ newSystemRoutingPermissionValue && !sessionInfos.isEmpty()
+ ? sessionInfos.get(0)
+ : mUserRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
+ notifySessionInfoChanged(systemSessionToReport);
+ }
}
public void dispose() {
@@ -1559,6 +1623,14 @@
pw.println(indent + "mPid=" + mPid);
pw.println(indent + "mHasConfigureWifiDisplayPermission="
+ mHasConfigureWifiDisplayPermission);
+ pw.println(
+ indent
+ + "mHasModifyAudioRoutingPermission="
+ + mHasModifyAudioRoutingPermission);
+ pw.println(
+ indent
+ + "mHasBluetoothRoutingPermission="
+ + mHasBluetoothRoutingPermission.get());
pw.println(indent + "hasSystemRoutingPermission=" + hasSystemRoutingPermission());
pw.println(indent + "mRouterId=" + mRouterId);
@@ -1581,6 +1653,19 @@
}
/**
+ * Sends the corresponding router an update for the given session.
+ *
+ * <p>Note: These updates are not directly visible to the app.
+ */
+ public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
+ try {
+ mRouter.notifySessionInfoChanged(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
+ }
+ }
+
+ /**
* Returns a filtered copy of {@code routes} that contains only the routes that are {@link
* MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
*/
@@ -2471,11 +2556,7 @@
@NonNull List<RouterRecord> routerRecords,
@NonNull RoutingSessionInfo sessionInfo) {
for (RouterRecord routerRecord : routerRecords) {
- try {
- routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
- }
+ routerRecord.notifySessionInfoChanged(sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4d134b6..b440e88 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -137,6 +138,7 @@
private final String mDefaultAudioRouteId;
private final String mBluetoothA2dpRouteId;
+ @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS)
public MediaRouterService(Context context) {
mService2 = new MediaRouter2ServiceImpl(context);
mContext = context;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6d2d2e4..426bc5e 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -55,7 +55,6 @@
SystemMediaRoute2Provider.class.getPackage().getName(),
SystemMediaRoute2Provider.class.getName());
- static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
static final String SYSTEM_SESSION_ID = "SYSTEM_SESSION";
private final AudioManager mAudioManager;
@@ -170,7 +169,7 @@
Bundle sessionHints) {
// Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
// a route ID different from the default route ID. The service should've filtered.
- if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
+ if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
return;
}
@@ -213,7 +212,7 @@
@Override
public void transferToRoute(long requestId, String sessionId, String routeId) {
- if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
+ if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
// The currently selected route is the default route.
return;
}
@@ -326,10 +325,11 @@
builder.addTransferableRoute(deviceRoute.getId());
}
mSelectedRouteId = selectedRoute.getId();
- mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
- .setSystemRoute(true)
- .setProviderId(mUniqueId)
- .build();
+ mDefaultRoute =
+ new MediaRoute2Info.Builder(MediaRoute2Info.ROUTE_ID_DEFAULT, selectedRoute)
+ .setSystemRoute(true)
+ .setProviderId(mUniqueId)
+ .build();
builder.addSelectedRoute(mSelectedRouteId);
for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
@@ -363,12 +363,13 @@
}
mSessionInfos.clear();
mSessionInfos.add(newSessionInfo);
- mDefaultSessionInfo = new RoutingSessionInfo.Builder(
- SYSTEM_SESSION_ID, "" /* clientPackageName */)
- .setProviderId(mUniqueId)
- .setSystemSession(true)
- .addSelectedRoute(DEFAULT_ROUTE_ID)
- .build();
+ mDefaultSessionInfo =
+ new RoutingSessionInfo.Builder(
+ SYSTEM_SESSION_ID, "" /* clientPackageName */)
+ .setProviderId(mUniqueId)
+ .setSystemSession(true)
+ .addSelectedRoute(MediaRoute2Info.ROUTE_ID_DEFAULT)
+ .build();
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index affe67d..5f28e56 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -14,6 +14,7 @@
* limitations under the License.
*/
package com.android.server.pm;
+
import static android.Manifest.permission.CONFIGURE_INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -333,9 +334,10 @@
}
private boolean isCrossProfilePackageAllowlisted(String packageName) {
+ int userId = mInjector.getCallingUserId();
return mInjector.withCleanCallingIdentity(() ->
mInjector.getDevicePolicyManagerInternal()
- .getAllCrossProfilePackages().contains(packageName));
+ .getAllCrossProfilePackages(userId).contains(packageName));
}
private boolean isCrossProfilePackageAllowlistedByDefault(String packageName) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 6032fec..8815834 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -238,6 +238,7 @@
UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
UserManager.DISALLOW_WIFI_TETHERING,
UserManager.DISALLOW_WIFI_DIRECT,
UserManager.DISALLOW_ADD_WIFI_CONFIG,
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4908529..7a43728 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3031,9 +3031,6 @@
}
}
- final boolean fromForegroundApp = Binder.withCleanCallingIdentity(() ->
- mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
-
synchronized (mLock) {
if (DEBUG) Slog.v(TAG, "setWallpaper which=0x" + Integer.toHexString(which));
WallpaperData wallpaper;
@@ -3066,7 +3063,7 @@
wallpaper.mSystemWasBoth = systemIsBoth;
wallpaper.mWhich = which;
wallpaper.setComplete = completion;
- wallpaper.fromForegroundApp = fromForegroundApp;
+ wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
wallpaper.cropHint.set(cropHint);
wallpaper.allowBackup = allowBackup;
wallpaper.mWallpaperDimAmount = getWallpaperDimAmount();
@@ -3153,27 +3150,28 @@
@SetWallpaperFlags int which, int userId) {
if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
- setWallpaperComponent(name, which, userId);
+ setWallpaperComponent(name, callingPackage, which, userId);
}
}
// ToDo: Remove this version of the function
@Override
public void setWallpaperComponent(ComponentName name) {
- setWallpaperComponent(name, UserHandle.getCallingUserId(), FLAG_SYSTEM);
+ setWallpaperComponent(name, "", UserHandle.getCallingUserId(), FLAG_SYSTEM);
}
@VisibleForTesting
- void setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which, int userId) {
+ void setWallpaperComponent(ComponentName name, String callingPackage,
+ @SetWallpaperFlags int which, int userId) {
if (mIsLockscreenLiveWallpaperEnabled) {
- setWallpaperComponentInternal(name, which, userId);
+ setWallpaperComponentInternal(name, callingPackage, which, userId);
} else {
- setWallpaperComponentInternalLegacy(name, which, userId);
+ setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
}
}
- private void setWallpaperComponentInternal(ComponentName name, @SetWallpaperFlags int which,
- int userIdIn) {
+ private void setWallpaperComponentInternal(ComponentName name, String callingPackage,
+ @SetWallpaperFlags int which, int userIdIn) {
if (DEBUG) {
Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
}
@@ -3209,6 +3207,7 @@
newWallpaper.imageWallpaperPending = false;
newWallpaper.mWhich = which;
newWallpaper.mSystemWasBoth = systemIsBoth;
+ newWallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
final WallpaperDestinationChangeHandler
liveSync = new WallpaperDestinationChangeHandler(
newWallpaper);
@@ -3280,7 +3279,7 @@
}
// TODO(b/266818039) Remove this method
- private void setWallpaperComponentInternalLegacy(ComponentName name,
+ private void setWallpaperComponentInternalLegacy(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
@@ -3320,6 +3319,7 @@
try {
wallpaper.imageWallpaperPending = false;
wallpaper.mWhich = which;
+ wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
boolean same = changingToSame(name, wallpaper);
if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
if (!same) {
@@ -3679,6 +3679,11 @@
}
}
+ private boolean isFromForegroundApp(String callingPackage) {
+ return Binder.withCleanCallingIdentity(() ->
+ mActivityManager.getPackageImportance(callingPackage) == IMPORTANCE_FOREGROUND);
+ }
+
/**
* Certain user types do not support wallpapers (e.g. managed profiles). The check is
* implemented through through the OP_WRITE_WALLPAPER AppOp.
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index fd74dac..0b7618d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -93,6 +93,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
+import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
import static android.content.res.Configuration.EMPTY;
@@ -1298,7 +1299,7 @@
+ info.getManifestMinAspectRatio());
}
pw.println(prefix + "supportsSizeChanges="
- + ActivityInfo.sizeChangesSupportModeToString(info.supportsSizeChanges()));
+ + ActivityInfo.sizeChangesSupportModeToString(supportsSizeChanges()));
if (info.configChanges != 0) {
pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
}
@@ -8127,7 +8128,7 @@
* aspect ratio.
*/
boolean shouldCreateCompatDisplayInsets() {
- switch (info.supportsSizeChanges()) {
+ switch (supportsSizeChanges()) {
case SIZE_CHANGES_SUPPORTED_METADATA:
case SIZE_CHANGES_SUPPORTED_OVERRIDE:
return false;
@@ -8154,6 +8155,26 @@
&& isActivityTypeStandardOrUndefined();
}
+ /**
+ * Returns whether the activity supports size changes.
+ */
+ @ActivityInfo.SizeChangesSupportMode
+ private int supportsSizeChanges() {
+ if (mLetterboxUiController.shouldOverrideForceNonResizeApp()) {
+ return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
+ }
+
+ if (info.supportsSizeChanges) {
+ return SIZE_CHANGES_SUPPORTED_METADATA;
+ }
+
+ if (mLetterboxUiController.shouldOverrideForceResizeApp()) {
+ return SIZE_CHANGES_SUPPORTED_OVERRIDE;
+ }
+
+ return SIZE_CHANGES_UNSUPPORTED_METADATA;
+ }
+
@Override
boolean hasSizeCompatBounds() {
return mSizeCompatBounds != null;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 0b960ec..b67ccd2 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -439,7 +439,9 @@
? mDisplayContent.getImeInputTarget().getActivityRecord() : null;
if (app != null) {
mDisplayContent.removeImeSurfaceImmediately();
- mDisplayContent.mAtmService.takeTaskSnapshot(app.getTask().mTaskId);
+ if (app.getTask() != null) {
+ mDisplayContent.mAtmService.takeTaskSnapshot(app.getTask().mTaskId);
+ }
}
} else {
// Disable IME icon explicitly when IME attached to the app in case
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a158e8d2..d83c861 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -17,6 +17,8 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
@@ -52,6 +54,7 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -189,6 +192,10 @@
// Corresponds to OVERRIDE_MIN_ASPECT_RATIO
private final boolean mIsOverrideMinAspectRatio;
+ // Corresponds to FORCE_RESIZE_APP
+ private final boolean mIsOverrideForceResizeApp;
+ // Corresponds to FORCE_NON_RESIZE_APP
+ private final boolean mIsOverrideForceNonResizeApp;
@Nullable
private final Boolean mBooleanPropertyAllowOrientationOverride;
@@ -196,6 +203,8 @@
private final Boolean mBooleanPropertyAllowDisplayOrientationOverride;
@Nullable
private final Boolean mBooleanPropertyAllowMinAspectRatioOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowForceResizeOverride;
/*
* WindowContainerListener responsible to make translucent activities inherit
@@ -311,6 +320,10 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
/* gatingCondition */ null,
PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+ mBooleanPropertyAllowForceResizeOverride =
+ readComponentProperty(packageManager, mActivityRecord.packageName,
+ /* gatingCondition */ null,
+ PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
mIsOverrideToPortraitOrientationEnabled =
@@ -342,6 +355,8 @@
mIsOverrideEnableCompatFakeFocusEnabled =
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS);
mIsOverrideMinAspectRatio = isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO);
+ mIsOverrideForceResizeApp = isCompatChangeEnabled(FORCE_RESIZE_APP);
+ mIsOverrideForceNonResizeApp = isCompatChangeEnabled(FORCE_NON_RESIZE_APP);
}
/**
@@ -533,6 +548,42 @@
}
/**
+ * Whether we should apply the force resize per-app override. When this override is applied it
+ * forces the packages it is applied to to be resizable. It won't change whether the app can be
+ * put into multi-windowing mode, but allow the app to resize without going into size-compat
+ * mode when the window container resizes, such as display size change or screen rotation.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceResizeApp() {
+ return shouldEnableWithOptInOverrideAndOptOutProperty(
+ /* gatingCondition */ () -> true,
+ mIsOverrideForceResizeApp,
+ mBooleanPropertyAllowForceResizeOverride);
+ }
+
+ /**
+ * Whether we should apply the force non resize per-app override. When this override is applied
+ * it forces the packages it is applied to to be non-resizable.
+ *
+ * <p>This method returns {@code true} when the following conditions are met:
+ * <ul>
+ * <li>Opt-out component property isn't enabled
+ * <li>Per-app override is enabled
+ * </ul>
+ */
+ boolean shouldOverrideForceNonResizeApp() {
+ return shouldEnableWithOptInOverrideAndOptOutProperty(
+ /* gatingCondition */ () -> true,
+ mIsOverrideForceNonResizeApp,
+ mBooleanPropertyAllowForceResizeOverride);
+ }
+
+ /**
* Sets whether an activity is relaunching after the app has called {@link
* android.app.Activity#setRequestedOrientation}.
*/
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index ee80a05..df968f9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -32,13 +32,17 @@
import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyState;
+import android.app.admin.IntentFilterPolicyKey;
import android.app.admin.PolicyKey;
import android.app.admin.PolicyUpdateReceiver;
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
@@ -1043,10 +1047,56 @@
}
if (updatedPackage != null) {
updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+ removePersistentPreferredActivityPoliciesForPackage(updatedPackage, userId);
}
});
}
+ private void removePersistentPreferredActivityPoliciesForPackage(
+ @NonNull String packageName, int userId) {
+ Set<PolicyKey> policyKeys = getLocalPolicyKeysSetByAllAdmins(
+ PolicyDefinition.GENERIC_PERSISTENT_PREFERRED_ACTIVITY, userId);
+ for (PolicyKey key : policyKeys) {
+ if (!(key instanceof IntentFilterPolicyKey)) {
+ throw new IllegalStateException("PolicyKey for "
+ + "PERSISTENT_PREFERRED_ACTIVITY is not of type "
+ + "IntentFilterPolicyKey");
+ }
+ IntentFilterPolicyKey parsedKey =
+ (IntentFilterPolicyKey) key;
+ IntentFilter intentFilter = Objects.requireNonNull(parsedKey.getIntentFilter());
+ PolicyDefinition<ComponentName> policyDefinition =
+ PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(intentFilter);
+ LinkedHashMap<EnforcingAdmin, PolicyValue<ComponentName>> policies =
+ getLocalPoliciesSetByAdmins(
+ policyDefinition,
+ userId);
+ IPackageManager packageManager = AppGlobals.getPackageManager();
+ for (EnforcingAdmin admin : policies.keySet()) {
+ if (policies.get(admin).getValue() != null
+ && policies.get(admin).getValue().getPackageName().equals(packageName)) {
+ try {
+ if (packageManager.getPackageInfo(
+ packageName, 0, userId) == null
+ || packageManager.getReceiverInfo(policies.get(admin).getValue(),
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ userId) == null) {
+ Slogf.e(TAG, String.format(
+ "Persistent preferred activity in package %s not found for "
+ + "user %d, removing policy for admin",
+ packageName, userId));
+ removeLocalPolicy(policyDefinition, admin, userId);
+ }
+ } catch (RemoteException re) {
+ // Shouldn't happen.
+ Slogf.wtf(TAG, "Error handling package changes", re);
+ }
+ }
+ }
+ }
+ }
+
private boolean isPackageInstalled(String packageName, int userId) {
try {
return AppGlobals.getPackageManager().getPackageInfo(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8f7e292..f875e34 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -11447,6 +11447,10 @@
|| isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller));
enforcingAdmin = getEnforcingAdminForCaller(who, callerPackageName);
}
+ if (!isPackageInstalledForUser(activity.getPackageName(), userId)) {
+ // Fail early as packageManager doesn't persist the activity if its not installed.
+ return;
+ }
mDevicePolicyEngine.setLocalPolicy(
PolicyDefinition.PERSISTENT_PREFERRED_ACTIVITY(filter),
enforcingAdmin,
@@ -13389,14 +13393,8 @@
PolicyDefinition<Boolean> policyDefinition =
PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
if (enabledFromThisOwner) {
- // TODO: Remove this special case - replace with breaking change to require
- // setGlobally to disable ADB
- if (key.equals(UserManager.DISALLOW_DEBUGGING_FEATURES) && parent) {
- setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
- } else {
- setLocalUserRestrictionInternal(
- admin, key, /* enabled= */ true, affectedUserId);
- }
+ setLocalUserRestrictionInternal(
+ admin, key, /* enabled= */ true, affectedUserId);
} else {
// Remove any local and global policy that was set by the admin
if (!policyDefinition.isLocalOnlyPolicy()) {
@@ -13914,7 +13912,6 @@
CallerIdentity caller = getCallerIdentity(who, callerPackage);
final int userId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId();
if (isPolicyEngineForFinanceFlagEnabled()) {
- // TODO: We need to ensure the delegate with DELEGATION_PACKAGE_ACCESS can do this
enforcePermission(MANAGE_DEVICE_POLICY_PACKAGE_STATE, caller.getPackageName(), userId);
} else {
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
@@ -16085,8 +16082,8 @@
}
@Override
- public List<String> getAllCrossProfilePackages() {
- return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+ public List<String> getAllCrossProfilePackages(int userId) {
+ return DevicePolicyManagerService.this.getAllCrossProfilePackages(userId);
}
@Override
@@ -20305,7 +20302,7 @@
}
@Override
- public List<String> getAllCrossProfilePackages() {
+ public List<String> getAllCrossProfilePackages(int userId) {
if (!mHasFeature) {
return Collections.emptyList();
}
@@ -20314,10 +20311,10 @@
isSystemUid(caller) || isRootUid(caller) || hasCallingPermission(
permission.INTERACT_ACROSS_USERS) || hasCallingPermission(
permission.INTERACT_ACROSS_USERS_FULL) || hasPermissionForPreflight(
- caller, permission.INTERACT_ACROSS_PROFILES));
+ caller, permission.INTERACT_ACROSS_PROFILES));
synchronized (getLockObject()) {
- final List<ActiveAdmin> admins = getProfileOwnerAdminsForCurrentProfileGroup();
+ final List<ActiveAdmin> admins = getProfileOwnerAdminsForProfileGroup(userId);
final List<String> packages = getCrossProfilePackagesForAdmins(admins);
packages.addAll(getDefaultCrossProfilePackages());
@@ -20346,11 +20343,10 @@
return new ArrayList<>(crossProfilePackages);
}
- private List<ActiveAdmin> getProfileOwnerAdminsForCurrentProfileGroup() {
+ private List<ActiveAdmin> getProfileOwnerAdminsForProfileGroup(int userId) {
synchronized (getLockObject()) {
final List<ActiveAdmin> admins = new ArrayList<>();
- int[] users = mUserManager.getProfileIdsWithDisabled(
- mInjector.userHandleGetCallingUserId());
+ int[] users = mUserManager.getProfileIdsWithDisabled(userId);
for (int i = 0; i < users.length; i++) {
final ComponentName componentName = getProfileOwnerAsUser(users[i]);
if (componentName != null) {
diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
index d128e68..4e46836 100644
--- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
+++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
@@ -201,7 +201,7 @@
}
private void mockCrossProfileAppWhitelisted() {
- when(mDevicePolicyManagerInternal.getAllCrossProfilePackages())
+ when(mDevicePolicyManagerInternal.getAllCrossProfilePackages(anyInt()))
.thenReturn(Lists.newArrayList(CROSS_PROFILE_APP_PACKAGE_NAME));
}
@@ -662,7 +662,7 @@
}
private void mockCrossProfileAppNotWhitelisted() {
- when(mDevicePolicyManagerInternal.getAllCrossProfilePackages())
+ when(mDevicePolicyManagerInternal.getAllCrossProfilePackages(anyInt()))
.thenReturn(new ArrayList<>());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 3d0163d..51e521d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -292,7 +292,8 @@
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
- mService.setWallpaperComponent(sImageWallpaperComponentName, FLAG_SYSTEM, testUserId);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM, testUserId);
verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
verifyCurrentSystemData(testUserId);
@@ -321,7 +322,8 @@
WallpaperManagerService.DisplayConnector connector =
mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
- mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
+ mService.setWallpaperComponent(sDefaultWallpaperComponent, sContext.getOpPackageName(),
+ FLAG_SYSTEM, testUserId);
verify(connector.mEngine).dispatchWallpaperCommand(
eq(COMMAND_REAPPLY), anyInt(), anyInt(), anyInt(), any());
@@ -465,7 +467,8 @@
public void testGetAdjustedWallpaperColorsOnDimming() throws RemoteException {
final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
- mService.setWallpaperComponent(sDefaultWallpaperComponent, FLAG_SYSTEM, testUserId);
+ mService.setWallpaperComponent(sDefaultWallpaperComponent, sContext.getOpPackageName(),
+ FLAG_SYSTEM, testUserId);
WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, testUserId);
// Mock a wallpaper data with color hints that support dark text and dark theme
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
index ccddb2fd..1475537 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncConnectionServiceTest.java
@@ -75,7 +75,7 @@
callMetadataSyncData.addCall(call);
mSyncConnectionService.mCrossDeviceSyncControllerCallback.processContextSyncMessage(
/* associationId= */ 0, callMetadataSyncData);
- verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any());
+ verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
index 5a0646c..6a939ab 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceCallTest.java
@@ -23,6 +23,7 @@
import android.telecom.Call;
import android.telecom.ParcelableCall;
import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
import android.testing.AndroidTestingRunner;
import androidx.test.InstrumentationRegistry;
@@ -45,7 +46,7 @@
@Test
public void updateCallDetails_uninitialized() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
assertWithMessage("Wrong status").that(crossDeviceCall.getStatus())
.isEqualTo(android.companion.Telecom.Call.UNKNOWN_STATUS);
@@ -55,7 +56,7 @@
@Test
public void updateCallDetails_ringing() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -70,7 +71,7 @@
@Test
public void updateCallDetails_ongoing() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -85,7 +86,7 @@
@Test
public void updateCallDetails_holding() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_HOLDING,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -99,7 +100,7 @@
@Test
public void updateCallDetails_cannotHold() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_MUTE));
@@ -113,7 +114,7 @@
@Test
public void updateCallDetails_cannotMute() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, Call.Details.CAPABILITY_HOLD));
@@ -127,7 +128,7 @@
@Test
public void updateCallDetails_transitionRingingToOngoing() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -150,7 +151,7 @@
@Test
public void updateSilencedIfRinging_ringing_silenced() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_RINGING,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -165,7 +166,7 @@
@Test
public void updateSilencedIfRinging_notRinging_notSilenced() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.updateCallDetails(createCallDetails(Call.STATE_ACTIVE,
Call.Details.CAPABILITY_HOLD | Call.Details.CAPABILITY_MUTE));
@@ -179,12 +180,11 @@
}
@Test
- public void getReadableCallerId_enterpriseCall_adminBlocked_ott() {
+ public void getReadableCallerId_enterpriseCall_adminBlocked_hasContact() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.mIsEnterprise = true;
- crossDeviceCall.mIsOtt = true;
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
@@ -195,76 +195,74 @@
}
@Test
- public void getReadableCallerId_enterpriseCall_adminUnblocked_ott() {
+ public void getReadableCallerId_enterpriseCall_adminUnblocked_hasContact() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.mIsEnterprise = true;
- crossDeviceCall.mIsOtt = true;
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
final String result = crossDeviceCall.getReadableCallerId(false);
assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CONTACT_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_enterpriseCall_adminBlocked_noContact() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext(),
+ mUninitializedCallDetails, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = true;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */
+ false));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminBlocked_noContact() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext(),
+ mUninitializedCallDetails, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */
+ false));
+
+ final String result = crossDeviceCall.getReadableCallerId(true);
+
+ assertWithMessage("Wrong caller id").that(result)
+ .isEqualTo(CALLER_DISPLAY_NAME);
+ }
+
+ @Test
+ public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_noContact() {
+ final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
+ InstrumentationRegistry.getTargetContext(),
+ mUninitializedCallDetails, /* callAudioState= */ null);
+ crossDeviceCall.mIsEnterprise = false;
+ crossDeviceCall.updateCallDetails(
+ createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0, /* hasContactName= */
+ false));
+
+ final String result = crossDeviceCall.getReadableCallerId(false);
+
+ assertWithMessage("Wrong caller id").that(result)
.isEqualTo(CALLER_DISPLAY_NAME);
}
@Test
- public void getReadableCallerId_enterpriseCall_adminBlocked_pstn() {
+ public void getReadableCallerId_nonEnterpriseCall_adminBlocked_hasContact() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
- mUninitializedCallDetails, /* callAudioState= */ null);
- crossDeviceCall.mIsEnterprise = true;
- crossDeviceCall.mIsOtt = false;
- crossDeviceCall.updateCallDetails(
- createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
-
- final String result = crossDeviceCall.getReadableCallerId(true);
-
- assertWithMessage("Wrong caller id").that(result)
- .isEqualTo(CALLER_DISPLAY_NAME);
- }
-
- @Test
- public void getReadableCallerId_nonEnterpriseCall_adminBlocked_ott() {
- final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.mIsEnterprise = false;
- crossDeviceCall.mIsOtt = true;
- crossDeviceCall.updateCallDetails(
- createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
-
- final String result = crossDeviceCall.getReadableCallerId(true);
-
- assertWithMessage("Wrong caller id").that(result)
- .isEqualTo(CALLER_DISPLAY_NAME);
- }
-
- @Test
- public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_ott() {
- final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
- mUninitializedCallDetails, /* callAudioState= */ null);
- crossDeviceCall.mIsEnterprise = false;
- crossDeviceCall.mIsOtt = true;
- crossDeviceCall.updateCallDetails(
- createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
-
- final String result = crossDeviceCall.getReadableCallerId(false);
-
- assertWithMessage("Wrong caller id").that(result)
- .isEqualTo(CALLER_DISPLAY_NAME);
- }
-
- @Test
- public void getReadableCallerId_nonEnterpriseCall_adminBlocked_pstn() {
- final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
- mUninitializedCallDetails, /* callAudioState= */ null);
- crossDeviceCall.mIsEnterprise = false;
- crossDeviceCall.mIsOtt = false;
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
@@ -275,12 +273,11 @@
}
@Test
- public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_pstn() {
+ public void getReadableCallerId_nonEnterpriseCall_adminUnblocked_hasContact() {
final CrossDeviceCall crossDeviceCall = new CrossDeviceCall(
- InstrumentationRegistry.getTargetContext().getPackageManager(),
+ InstrumentationRegistry.getTargetContext(),
mUninitializedCallDetails, /* callAudioState= */ null);
crossDeviceCall.mIsEnterprise = false;
- crossDeviceCall.mIsOtt = false;
crossDeviceCall.updateCallDetails(
createCallDetails(Call.STATE_ACTIVE, /* capabilities= */ 0));
@@ -291,10 +288,17 @@
}
private Call.Details createCallDetails(int state, int capabilities) {
+ return createCallDetails(state, capabilities, /* hasContactName= */ true);
+ }
+
+ private Call.Details createCallDetails(int state, int capabilities, boolean hasContactName) {
final ParcelableCall.ParcelableCallBuilder parcelableCallBuilder =
new ParcelableCall.ParcelableCallBuilder();
parcelableCallBuilder.setCallerDisplayName(CALLER_DISPLAY_NAME);
- parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME);
+ if (hasContactName) {
+ parcelableCallBuilder.setContactDisplayName(CONTACT_DISPLAY_NAME);
+ }
+ parcelableCallBuilder.setCallerDisplayNamePresentation(TelecomManager.PRESENTATION_ALLOWED);
parcelableCallBuilder.setCapabilities(capabilities);
parcelableCallBuilder.setState(state);
parcelableCallBuilder.setConferenceableCallIds(Collections.emptyList());
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
index 33e7cd2..7688fcb 100644
--- a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/CrossDeviceSyncControllerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -211,4 +212,62 @@
verify(mMockTelecomManager, times(1)).registerPhoneAccount(any());
verify(mMockTelecomManager, times(1)).unregisterPhoneAccount(any());
}
+
+ @Test
+ public void updateCalls_newCall() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ call.setFacilitator(new CallMetadataSyncData.CallFacilitator("name", "com.android.test"));
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, times(1)).addNewIncomingCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_newCall_noFacilitator() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, times(0)).addNewIncomingCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_existingCall() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId()));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ }
+
+ @Test
+ public void updateCalls_removedCall() {
+ final CallMetadataSyncData.Call call = new CallMetadataSyncData.Call();
+ call.setId("123abc");
+ final CallMetadataSyncData callMetadataSyncData = new CallMetadataSyncData();
+ callMetadataSyncData.addCall(call);
+ final CrossDeviceSyncController.CallManager callManager =
+ new CrossDeviceSyncController.CallManager(mMockContext,
+ new CrossDeviceSyncController.PhoneAccountManager(mMockContext));
+ callManager.mCallIds.put(/* associationId= */ 0, Set.of(call.getId(), "fakeCallId"));
+ callManager.updateCalls(/* associationId= */ 0, callMetadataSyncData);
+ verify(mMockTelecomManager, never()).addNewIncomingCall(any(), any());
+ assertWithMessage("Hasn't removed the id of the removed call")
+ .that(callManager.mCallIds)
+ .containsExactly(/* associationId= */ 0, Set.of(call.getId()));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 4881012..6684150 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -75,6 +75,10 @@
private static final DeviceState UNSUPPORTED_DEVICE_STATE =
new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
+ private static final int[] SUPPORTED_DEVICE_STATE_IDENTIFIERS =
+ new int[]{DEFAULT_DEVICE_STATE.getIdentifier(), OTHER_DEVICE_STATE.getIdentifier(),
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()};
+
private static final int FAKE_PROCESS_ID = 100;
private TestDeviceStatePolicy mPolicy;
@@ -267,14 +271,26 @@
public void getDeviceStateInfo() throws RemoteException {
DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
assertNotNull(info);
- assertArrayEquals(info.supportedStates,
- new int[] { DEFAULT_DEVICE_STATE.getIdentifier(),
- OTHER_DEVICE_STATE.getIdentifier(),
- DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()});
+ assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
}
+ @Test
+ public void getDeviceStateInfo_baseStateAndCommittedStateNotSet() throws RemoteException {
+ // Create a provider and a service without an initial base state.
+ mProvider = new TestDeviceStateProvider(null /* initialState */);
+ mPolicy = new TestDeviceStatePolicy(mProvider);
+ setupDeviceStateManagerService();
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+
+ DeviceStateInfo info = mService.getBinderService().getDeviceStateInfo();
+
+ assertArrayEquals(info.supportedStates, SUPPORTED_DEVICE_STATE_IDENTIFIERS);
+ assertEquals(info.baseState, INVALID_DEVICE_STATE);
+ assertEquals(info.currentState, INVALID_DEVICE_STATE);
+ }
+
@FlakyTest(bugId = 223153452)
@Test
public void registerCallback() throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 67e7470..7d507e9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
+import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH;
@@ -43,6 +45,7 @@
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
@@ -942,6 +945,118 @@
}
@Test
+ @EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_overrideEnabled_returnsTrue() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_overrideDisabled_returnsFalse() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @EnableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_propertyFalse_overrideEnabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_RESIZE_APP})
+ public void testshouldOverrideForceResizeApp_propertyFalse_noOverride_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceResizeApp());
+ }
+
+ @Test
+ @EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_overrideEnabled_returnsTrue() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
+ @EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_overrideDisabled_returnsFalse() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
+ @EnableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_propertyFalse_overrideEnabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
+ @DisableCompatChanges({FORCE_NON_RESIZE_APP})
+ public void testshouldOverrideForceNonResizeApp_propertyFalse_noOverride_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideForceNonResizeApp());
+ }
+
+ @Test
public void testgetFixedOrientationLetterboxAspectRatio_splitScreenAspectEnabled() {
doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
.isCameraCompatTreatmentEnabled(anyBoolean());
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 9fb5509..13945a1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -366,10 +366,10 @@
try {
int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName,
PackageManager.PackageInfoFlags.of(0));
- if (uid != mOriginatorIdentity.uid) {
- throw new SecurityException("Package name: " +
- mOriginatorIdentity.packageName + "with uid: " + uid
- + "attempted to spoof as: " + mOriginatorIdentity.uid);
+ if (!UserHandle.isSameApp(uid, mOriginatorIdentity.uid)) {
+ throw new SecurityException("Uid " + mOriginatorIdentity.uid +
+ " attempted to spoof package name " +
+ mOriginatorIdentity.packageName + " with uid: " + uid);
}
} catch (PackageManager.NameNotFoundException e) {
throw new SecurityException("Package name not found: "
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index edaaf3f..248cc26 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -962,7 +962,7 @@
final DetectorSession session = mDetectorSessions.get(
HotwordDetector.DETECTOR_TYPE_VISUAL_QUERY_DETECTOR);
if (session == null || session.isDestroyed()) {
- Slog.v(TAG, "Not found the look and talk perceiver");
+ Slog.v(TAG, "Not found the visual query detector");
return null;
}
return (VisualQueryDetectorSession) session;