Merge "Don't let transition tracing exceptions bubble up" 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/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 98fe616..ac9c497 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -17,6 +17,7 @@
package android.app;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -37,6 +38,8 @@
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+import static android.permission.PermissionCheckerManager.PERMISSION_HARD_DENIED;
+import static android.permission.PermissionCheckerManager.PERMISSION_SOFT_DENIED;
import android.Manifest;
import android.annotation.IntDef;
@@ -268,7 +271,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -284,7 +288,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -302,7 +307,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_DATA_SYNC /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -320,7 +326,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PLAYBACK /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -341,7 +348,8 @@
new RolePermission(RoleManager.ROLE_DIALER)
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_PHONE_CALL /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -362,7 +370,8 @@
new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_LOCATION /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -392,7 +401,8 @@
new UsbAccessoryPermission(),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_CONNECTED_DEVICE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -413,7 +423,8 @@
new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PROJECTION /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -434,7 +445,8 @@
new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_CAMERA /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -459,7 +471,8 @@
new RegularPermission(Manifest.permission.RECORD_AUDIO),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MICROPHONE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -481,7 +494,8 @@
new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -499,7 +513,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_REMOTE_MESSAGING /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -521,7 +536,8 @@
new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_SYSTEM_EXEMPTED /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -537,7 +553,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -555,7 +572,8 @@
}, true),
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -573,7 +591,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_SPECIAL_USE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -641,6 +660,29 @@
public @interface ForegroundServicePolicyCheckCode{}
/**
+ * Whether or not to require that app to have actual access to certain foreground only
+ * permissions before starting the foreground service.
+ *
+ * <p>
+ * Examples here are microphone, camera and fg location related permissions.
+ * When the user grants the permission, its permission state is set to "granted",
+ * but the actual capability to access these sensors, is to be evaluated according to
+ * its process state. The Android {@link android.os.Build.VERSION_CODES#R} introduced
+ * the while-in-use permission, basically the background-started FGS will not have access
+ * to these sensors. In this context, there is no legitimate reasons to start a FGS from
+ * the background with these types. This flag controls the behavior of the enforcement,
+ * when it's enabled, in the aforementioned case, the FGS start will result in
+ * a SecurityException. </p>
+ */
+ private static final String FGS_TYPE_FG_PERM_ENFORCEMENT_FLAG =
+ "fgs_type_fg_perm_enforcement_flag";
+
+ /**
+ * The default value to the {@link #FGS_TYPE_FG_PERM_ENFORCEMENT_FLAG}.
+ */
+ private static final boolean DEFAULT_FGS_TYPE_FG_PERM_ENFORCEMENT_FLAG_VALUE = true;
+
+ /**
* @return The policy info for the given type.
*/
@NonNull
@@ -682,6 +724,11 @@
}
}
+ private static boolean isFgsTypeFgPermissionEnforcementEnabled() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ FGS_TYPE_FG_PERM_ENFORCEMENT_FLAG, DEFAULT_FGS_TYPE_FG_PERM_ENFORCEMENT_FLAG_VALUE);
+ }
+
/**
* Constructor.
*
@@ -739,6 +786,12 @@
final boolean mPermissionEnforcementFlagDefaultValue;
/**
+ * Whether or not the permissions here are limited to foreground only.
+ * Typical examples are microphone/camera/location.
+ */
+ final boolean mForegroundOnlyPermission;
+
+ /**
* A customized check for the permissions.
*/
@Nullable ForegroundServiceTypePermission mCustomPermission;
@@ -774,7 +827,8 @@
@Nullable ForegroundServiceTypePermissions allOfPermissions,
@Nullable ForegroundServiceTypePermissions anyOfPermissions,
@Nullable String permissionEnforcementFlag,
- boolean permissionEnforcementFlagDefaultValue) {
+ boolean permissionEnforcementFlagDefaultValue,
+ boolean foregroundOnlyPermission) {
mType = type;
mDeprecationChangeId = deprecationChangeId;
mDisabledChangeId = disabledChangeId;
@@ -783,6 +837,7 @@
mPermissionEnforcementFlag = permissionEnforcementFlag;
mPermissionEnforcementFlagDefaultValue = permissionEnforcementFlagDefaultValue;
mPermissionEnforcementFlagValue = permissionEnforcementFlagDefaultValue;
+ mForegroundOnlyPermission = foregroundOnlyPermission;
}
/**
@@ -885,6 +940,14 @@
}
/**
+ * Whether or not the permissions here are limited to foreground only.
+ * Typical examples are microphone/camera/location.
+ */
+ public boolean hasForegroundOnlyPermission() {
+ return mForegroundOnlyPermission;
+ }
+
+ /**
* Override the type disabling change Id.
*
* For test only.
@@ -1082,33 +1145,45 @@
@PackageManager.PermissionResult
int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
int callerPid, String packageName, boolean allowWhileInUse) {
- // Simple case, check if it's already granted.
- @PermissionCheckerManager.PermissionResult int result;
- if ((result = PermissionChecker.checkPermissionForPreflight(context, name, callerPid,
- callerUid, packageName)) == PermissionCheckerManager.PERMISSION_GRANTED) {
- return PERMISSION_GRANTED;
+ @PermissionCheckerManager.PermissionResult final int result =
+ PermissionChecker.checkPermissionForPreflight(context, name,
+ callerPid, callerUid, packageName);
+ if (result == PERMISSION_HARD_DENIED) {
+ // If the user didn't grant this permission at all.
+ return PERMISSION_DENIED;
}
- if (allowWhileInUse && result == PermissionCheckerManager.PERMISSION_SOFT_DENIED) {
- // Check its appops
- final int opCode = AppOpsManager.permissionToOpCode(name);
- final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
- if (opCode != AppOpsManager.OP_NONE) {
- final int currentMode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid,
- packageName);
- if (currentMode == MODE_FOREGROUND) {
- // It's in foreground only mode and we're allowing while-in-use.
- return PERMISSION_GRANTED;
- } else if (currentMode == MODE_IGNORED) {
- // If it's soft denied with the mode "ignore", semantically it's a silent
- // failure and no exception should be thrown, we might not want to allow
- // the FGS. However, since the user has agreed with this permission
- // (otherwise it's going to be a hard denial), and we're allowing
- // while-in-use here, it's safe to allow the FGS run here.
- return PERMISSION_GRANTED;
- }
- }
+ final int opCode = AppOpsManager.permissionToOpCode(name);
+ if (opCode == AppOpsManager.OP_NONE) {
+ // Simple case, check if it's already granted.
+ return result == PermissionCheckerManager.PERMISSION_GRANTED
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
}
- return PERMISSION_DENIED;
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final int mode = appOpsManager.unsafeCheckOpRawNoThrow(opCode, callerUid, packageName);
+ switch (mode) {
+ case MODE_ALLOWED:
+ // The appop is just allowed, plain and simple.
+ return PERMISSION_GRANTED;
+ case MODE_DEFAULT:
+ // Follow the permission check result.
+ return result == PermissionCheckerManager.PERMISSION_GRANTED
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ case MODE_FOREGROUND:
+ // If the enforcement flag is OFF, we silently allow it. Or, if it's in
+ // the foreground only mode and we're allowing while-in-use, allow it.
+ return !isFgsTypeFgPermissionEnforcementEnabled() || allowWhileInUse
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ case MODE_IGNORED:
+ // If it's soft denied with the mode "ignore", semantically it's a silent
+ // failure and no exception should be thrown, we might not want to allow
+ // the FGS. However, since the user has agreed with this permission
+ // (otherwise it's going to be a hard denial), and we're allowing
+ // while-in-use here, it's safe to allow the FGS run here.
+ return allowWhileInUse && result == PERMISSION_SOFT_DENIED
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ default:
+ return PERMISSION_DENIED;
+ }
}
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a6313db..776e34b 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -571,6 +571,15 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long MEDIA_CONTROL_SESSION_ACTIONS = 203800354L;
+ /**
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications will be
+ * required to include a non-empty title, either in the {@link android.media.MediaMetadata} or
+ * notification title.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long MEDIA_CONTROL_REQUIRES_TITLE = 274775190L;
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1217,6 +1226,21 @@
}
/**
+ * Checks whether the given package must include a non-empty title for its media controls.
+ *
+ * @param packageName App posting media controls
+ * @param user Current user handle
+ * @return true if the app is required to provide a non-empty title
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE})
+ public static boolean isMediaTitleRequiredForApp(String packageName, UserHandle user) {
+ return CompatChanges.isChangeEnabled(MEDIA_CONTROL_REQUIRES_TITLE, packageName, user);
+ }
+
+ /**
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
* a system activity that captures content on the screen to take a screenshot.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e9fb811..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.
@@ -9862,6 +9924,9 @@
* profile owner of an organization-owned managed profile.
* @throws IllegalArgumentException if called on the parent profile and the package
* provided is not a pre-installed system package.
+ * @throws IllegalStateException while trying to set default sms app on the profile and
+ * {@link ManagedSubscriptionsPolicy#TYPE_ALL_MANAGED_SUBSCRIPTIONS}
+ * policy is not set.
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_DEFAULT_SMS, conditional = true)
public void setDefaultSmsApplication(@Nullable ComponentName admin,
@@ -11472,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
@@ -11504,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
@@ -11541,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
@@ -11689,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.
@@ -11728,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.
@@ -11852,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.
@@ -11984,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
@@ -12013,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.
@@ -12065,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.
@@ -12089,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.
@@ -12574,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.
@@ -12611,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
@@ -15492,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();
}
@@ -15720,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.
@@ -15746,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/app/backup/BackupManagerMonitorWrapper.java b/core/java/android/app/backup/BackupManagerMonitorWrapper.java
index 0b18995..39bfb1b 100644
--- a/core/java/android/app/backup/BackupManagerMonitorWrapper.java
+++ b/core/java/android/app/backup/BackupManagerMonitorWrapper.java
@@ -16,9 +16,12 @@
package android.app.backup;
+import android.annotation.Nullable;
import android.os.Bundle;
import android.os.RemoteException;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Wrapper around {@link BackupManagerMonitor} that helps with IPC between the caller of backup
* APIs and the backup service.
@@ -26,16 +29,24 @@
* The caller implements {@link BackupManagerMonitor} and passes it into framework APIs that run on
* the caller's process. Those framework APIs will then wrap it around this class when doing the
* actual IPC.
+ *
+ * @hide
*/
-class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub {
+@VisibleForTesting
+public class BackupManagerMonitorWrapper extends IBackupManagerMonitor.Stub {
+ @Nullable
private final BackupManagerMonitor mMonitor;
- BackupManagerMonitorWrapper(BackupManagerMonitor monitor) {
+ public BackupManagerMonitorWrapper(@Nullable BackupManagerMonitor monitor) {
mMonitor = monitor;
}
@Override
public void onEvent(final Bundle event) throws RemoteException {
+ if (mMonitor == null) {
+ // It's valid for the underlying monitor to be null, so just return.
+ return;
+ }
mMonitor.onEvent(event);
}
}
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/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 7ac8f37..c3df17d 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -27,6 +27,7 @@
import android.annotation.TestApi;
import android.annotation.UserHandleAware;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.AppOpsManager.Mode;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
@@ -110,8 +111,8 @@
component,
targetUser.getIdentifier(),
true,
- null,
- null);
+ mContext.getActivityToken(),
+ ActivityOptions.makeBasic().toBundle());
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 408f7ed..269bec2 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -138,11 +138,6 @@
*/
private final boolean mIsSdkLibrary;
- /**
- * Indicates if this package allows an installer to declare update ownership of it.
- */
- private final boolean mAllowUpdateOwnership;
-
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
int versionCodeMajor, int revisionCode, int installLocation,
@@ -153,7 +148,7 @@
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean allowUpdateOwnership) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -187,7 +182,6 @@
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
- mAllowUpdateOwnership = allowUpdateOwnership;
}
/**
@@ -480,9 +474,6 @@
return mRollbackDataPolicy;
}
- /**
- * Indicates if this app contains a {@link android.app.admin.DeviceAdminReceiver}.
- */
@DataClass.Generated.Member
public boolean isHasDeviceAdminReceiver() {
return mHasDeviceAdminReceiver;
@@ -496,19 +487,11 @@
return mIsSdkLibrary;
}
- /**
- * Indicates if this package allows an installer to declare update ownership of it.
- */
- @DataClass.Generated.Member
- public boolean isAllowUpdateOwnership() {
- return mAllowUpdateOwnership;
- }
-
@DataClass.Generated(
- time = 1680122754650L,
+ time = 1643063342990L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index d209b35..820bb1b 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -127,8 +127,7 @@
null /* isFeatureSplits */, null /* usesSplitNames */,
null /* configForSplit */, null /* splitApkPaths */,
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
- null /* requiredSplitTypes */, null, /* splitTypes */
- baseApk.isAllowUpdateOwnership()));
+ null /* requiredSplitTypes */, null /* splitTypes */));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -154,8 +153,7 @@
null /* isFeatureSplits */, null /* usesSplitNames */,
null /* configForSplit */, null /* splitApkPaths */,
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
- null /* requiredSplitTypes */, null, /* splitTypes */
- baseApk.isAllowUpdateOwnership()));
+ null /* requiredSplitTypes */, null /* splitTypes */));
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -301,8 +299,7 @@
return input.success(
new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits,
usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes,
- baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes,
- baseApk.isAllowUpdateOwnership()));
+ baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes));
}
/**
@@ -429,8 +426,6 @@
"isFeatureSplit", false);
boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"isSplitRequired", false);
- boolean allowUpdateOwnership = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "allowUpdateOwnership", true);
String configForSplit = parser.getAttributeValue(null, "configForSplit");
int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION;
@@ -614,7 +609,7 @@
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, allowUpdateOwnership));
+ hasDeviceAdminReceiver, isSdkLibrary));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index e24b932..e2789c9 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -110,16 +110,10 @@
*/
private final boolean mIsSdkLibrary;
- /**
- * Indicates if this package allows an installer to declare update ownership of it.
- */
- private final boolean mAllowUpdateOwnership;
-
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes,
- int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes,
- boolean allowUpdateOwnership) {
+ int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes) {
// The following paths may be different from the path in ApkLite because we
// move or rename the APK files. Use parameters to indicate the correct paths.
mPath = path;
@@ -150,7 +144,6 @@
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
- mAllowUpdateOwnership = allowUpdateOwnership;
}
/**
@@ -421,19 +414,12 @@
return mIsSdkLibrary;
}
- /**
- * Indicates if this package allows an installer to declare update ownership of it.
- */
- @DataClass.Generated.Member
- public boolean isAllowUpdateOwnership() {
- return mAllowUpdateOwnership;
- }
-
@DataClass.Generated(
- time = 1680125514341L,
+ time = 1643132127068L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures =
+ "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/hardware/CameraSessionStats.java b/core/java/android/hardware/CameraSessionStats.java
index d59295e..1c2cbbc 100644
--- a/core/java/android/hardware/CameraSessionStats.java
+++ b/core/java/android/hardware/CameraSessionStats.java
@@ -65,6 +65,7 @@
private String mUserTag;
private int mVideoStabilizationMode;
private int mSessionIndex;
+ private CameraExtensionSessionStats mCameraExtensionSessionStats;
public CameraSessionStats() {
mFacing = -1;
@@ -82,6 +83,7 @@
mStreamStats = new ArrayList<CameraStreamStats>();
mVideoStabilizationMode = -1;
mSessionIndex = 0;
+ mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
public CameraSessionStats(String cameraId, int facing, int newCameraState,
@@ -101,6 +103,7 @@
mInternalReconfigure = internalReconfigure;
mStreamStats = new ArrayList<CameraStreamStats>();
mSessionIndex = sessionIdx;
+ mCameraExtensionSessionStats = new CameraExtensionSessionStats();
}
public static final @android.annotation.NonNull Parcelable.Creator<CameraSessionStats> CREATOR =
@@ -145,6 +148,7 @@
dest.writeString(mUserTag);
dest.writeInt(mVideoStabilizationMode);
dest.writeInt(mSessionIndex);
+ mCameraExtensionSessionStats.writeToParcel(dest, 0);
}
public void readFromParcel(Parcel in) {
@@ -170,6 +174,7 @@
mUserTag = in.readString();
mVideoStabilizationMode = in.readInt();
mSessionIndex = in.readInt();
+ mCameraExtensionSessionStats = CameraExtensionSessionStats.CREATOR.createFromParcel(in);
}
public String getCameraId() {
@@ -243,4 +248,8 @@
public int getSessionIndex() {
return mSessionIndex;
}
+
+ public CameraExtensionSessionStats getExtensionSessionStats() {
+ return mCameraExtensionSessionStats;
+ }
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c6fc69e..51501b5 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
+import android.hardware.CameraExtensionSessionStats;
import android.hardware.CameraStatus;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceListener;
@@ -1727,6 +1728,30 @@
}
/**
+ * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
+ * currently active session. Validation is done downstream.
+ *
+ * @param extStats Extension Session stats to be logged by cameraservice
+ *
+ * @return the key to be used with the next call.
+ * See {@link ICameraService#reportExtensionSessionStats}.
+ * @hide
+ */
+ public static String reportExtensionSessionStats(CameraExtensionSessionStats extStats) {
+ ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
+ if (cameraService == null) {
+ Log.e(TAG, "CameraService not available. Not reporting extension stats.");
+ return "";
+ }
+ try {
+ return cameraService.reportExtensionSessionStats(extStats);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to report extension session stats to cameraservice.", e);
+ }
+ return "";
+ }
+
+ /**
* A per-process global camera manager instance, to retain a connection to the camera service,
* and to distribute camera availability notices to API-registered callbacks
*/
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index e787779..d87226c 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -53,6 +53,7 @@
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.utils.ExtensionSessionStatsAggregator;
import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageReader;
@@ -96,6 +97,7 @@
private CameraCaptureSession mCaptureSession = null;
private ISessionProcessorImpl mSessionProcessor = null;
private final InitializeSessionHandler mInitializeHandler;
+ private final ExtensionSessionStatsAggregator mStatsAggregator;
private boolean mInitialized;
@@ -205,6 +207,10 @@
extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface,
burstCaptureSurface, postviewSurface, config.getStateCallback(),
config.getExecutor(), sessionId);
+
+ ret.mStatsAggregator.setClientName(ctx.getOpPackageName());
+ ret.mStatsAggregator.setExtensionType(config.getExtension());
+
ret.initialize();
return ret;
@@ -234,6 +240,9 @@
mInitializeHandler = new InitializeSessionHandler();
mSessionId = sessionId;
mInterfaceLock = cameraDevice.mInterfaceLock;
+
+ mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
+ /*isAdvanced=*/true);
}
/**
@@ -523,11 +532,26 @@
Log.e(TAG, "Failed to stop the repeating request or end the session,"
+ " , extension service does not respond!") ;
}
+ // Commit stats before closing the capture session
+ mStatsAggregator.commit(/*isFinal*/true);
mCaptureSession.close();
}
}
}
+ /**
+ * Called by {@link CameraDeviceImpl} right before the capture session is closed, and before it
+ * calls {@link #release}
+ */
+ public void commitStats() {
+ synchronized (mInterfaceLock) {
+ if (mInitialized) {
+ // Only commit stats if a capture session was initialized
+ mStatsAggregator.commit(/*isFinal*/true);
+ }
+ }
+ }
+
public void release(boolean skipCloseNotification) {
boolean notifyClose = false;
@@ -608,6 +632,8 @@
public void onConfigured(@NonNull CameraCaptureSession session) {
synchronized (mInterfaceLock) {
mCaptureSession = session;
+ // Commit basic stats as soon as the capture session is created
+ mStatsAggregator.commit(/*isFinal*/false);
}
try {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index b8e451c..e23bbc6 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -700,6 +700,14 @@
+ " input configuration yet.");
}
+ if (mCurrentExtensionSession != null) {
+ mCurrentExtensionSession.commitStats();
+ }
+
+ if (mCurrentAdvancedExtensionSession != null) {
+ mCurrentAdvancedExtensionSession.commitStats();
+ }
+
// Notify current session that it's going away, before starting camera operations
// After this call completes, the session is not allowed to call into CameraDeviceImpl
if (mCurrentSession != null) {
@@ -1414,6 +1422,15 @@
mOfflineSwitchService = null;
}
+ // Let extension sessions commit stats before disconnecting remoteDevice
+ if (mCurrentExtensionSession != null) {
+ mCurrentExtensionSession.commitStats();
+ }
+
+ if (mCurrentAdvancedExtensionSession != null) {
+ mCurrentAdvancedExtensionSession.commitStats();
+ }
+
if (mRemoteDevice != null) {
mRemoteDevice.disconnect();
mRemoteDevice.unlinkToDeath(this, /*flags*/0);
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 8e7c7e0..9d2bb7e 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -30,7 +30,6 @@
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraExtensionCharacteristics;
import android.hardware.camera2.CameraExtensionSession;
-import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
@@ -49,6 +48,7 @@
import android.hardware.camera2.params.ExtensionSessionConfiguration;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.utils.ExtensionSessionStatsAggregator;
import android.hardware.camera2.utils.SurfaceUtils;
import android.media.Image;
import android.media.ImageReader;
@@ -90,6 +90,7 @@
private final int mSessionId;
private final Set<CaptureRequest.Key> mSupportedRequestKeys;
private final Set<CaptureResult.Key> mSupportedResultKeys;
+ private final ExtensionSessionStatsAggregator mStatsAggregator;
private boolean mCaptureResultsSupported;
private CameraCaptureSession mCaptureSession = null;
@@ -242,6 +243,9 @@
extensionChars.getAvailableCaptureRequestKeys(config.getExtension()),
extensionChars.getAvailableCaptureResultKeys(config.getExtension()));
+ session.mStatsAggregator.setClientName(ctx.getOpPackageName());
+ session.mStatsAggregator.setExtensionType(config.getExtension());
+
session.initialize();
return session;
@@ -280,6 +284,9 @@
mSupportedResultKeys = resultKeys;
mCaptureResultsSupported = !resultKeys.isEmpty();
mInterfaceLock = cameraDevice.mInterfaceLock;
+
+ mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(),
+ /*isAdvanced=*/false);
}
private void initializeRepeatingRequestPipeline() throws RemoteException {
@@ -793,11 +800,27 @@
new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler);
}
+ mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session
mCaptureSession.close();
}
}
}
+ /**
+ * Called by {@link CameraDeviceImpl} right before the capture session is closed, and before it
+ * calls {@link #release}
+ *
+ * @hide
+ */
+ public void commitStats() {
+ synchronized (mInterfaceLock) {
+ if (mInitialized) {
+ // Only commit stats if a capture session was initialized
+ mStatsAggregator.commit(/*isFinal*/true);
+ }
+ }
+ }
+
private void setInitialCaptureRequest(List<CaptureStageImpl> captureStageList,
InitialRequestHandler requestHandler)
throws CameraAccessException {
@@ -955,6 +978,8 @@
public void onConfigured(@NonNull CameraCaptureSession session) {
synchronized (mInterfaceLock) {
mCaptureSession = session;
+ // Commit basic stats as soon as the capture session is created
+ mStatsAggregator.commit(/*isFinal*/false);
try {
finishPipelineInitialization();
CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
new file mode 100644
index 0000000..8cd5e83
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java
@@ -0,0 +1,121 @@
+/*
+ * 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 android.hardware.camera2.utils;
+
+import android.annotation.NonNull;
+import android.hardware.CameraExtensionSessionStats;
+import android.hardware.camera2.CameraManager;
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Utility class to aggregate metrics specific to Camera Extensions and pass them to
+ * {@link CameraManager}. {@link android.hardware.camera2.CameraExtensionSession} should call
+ * {@link #commit} before closing the session.
+ *
+ * @hide
+ */
+public class ExtensionSessionStatsAggregator {
+ private static final boolean DEBUG = false;
+ private static final String TAG = ExtensionSessionStatsAggregator.class.getSimpleName();
+
+ private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
+ private final Object mLock = new Object(); // synchronizes access to all fields of the class
+ private boolean mIsDone = false; // marks the aggregator as "done".
+ // Mutations and commits become no-op if this is true.
+ private final CameraExtensionSessionStats mStats;
+
+ public ExtensionSessionStatsAggregator(@NonNull String cameraId, boolean isAdvanced) {
+ if (DEBUG) {
+ Log.v(TAG, "Creating new Extension Session Stats Aggregator");
+ }
+ mStats = new CameraExtensionSessionStats();
+ mStats.key = "";
+ mStats.cameraId = cameraId;
+ mStats.isAdvanced = isAdvanced;
+ }
+
+ /**
+ * Set client package name
+ *
+ * @param clientName package name of the client that these stats are associated with.
+ */
+ public void setClientName(@NonNull String clientName) {
+ synchronized (mLock) {
+ if (mIsDone) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Setting clientName: " + clientName);
+ }
+ mStats.clientName = clientName;
+ }
+ }
+
+ /**
+ * Set extension type.
+ *
+ * @param extensionType Type of extension. Must match one of
+ * {@code CameraExtensionCharacteristics#EXTENSION_*}
+ */
+ public void setExtensionType(int extensionType) {
+ synchronized (mLock) {
+ if (mIsDone) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Setting type: " + extensionType);
+ }
+ mStats.type = extensionType;
+ }
+ }
+
+ /**
+ * Asynchronously commits the stats to CameraManager on a background thread.
+ *
+ * @param isFinal marks the stats as final and prevents any further commits or changes. This
+ * should be set to true when the stats are considered final for logging,
+ * for example right before the capture session is about to close
+ */
+ public void commit(boolean isFinal) {
+ // Call binder on a background thread to reduce latencies from metrics logging.
+ mExecutor.execute(() -> {
+ synchronized (mLock) {
+ if (mIsDone) {
+ return;
+ }
+ mIsDone = isFinal;
+ if (DEBUG) {
+ Log.v(TAG, "Committing: " + prettyPrintStats(mStats));
+ }
+ mStats.key = CameraManager.reportExtensionSessionStats(mStats);
+ }
+ });
+ }
+
+ private static String prettyPrintStats(@NonNull CameraExtensionSessionStats stats) {
+ return CameraExtensionSessionStats.class.getSimpleName() + ":\n"
+ + " key: '" + stats.key + "'\n"
+ + " cameraId: '" + stats.cameraId + "'\n"
+ + " clientName: '" + stats.clientName + "'\n"
+ + " type: '" + stats.type + "'\n"
+ + " isAdvanced: '" + stats.isAdvanced + "'\n";
+ }
+}
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/os/UserManager.java b/core/java/android/os/UserManager.java
index 8606687..7d68b44 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -250,6 +250,10 @@
* use {@link android.accounts.AccountManager} APIs to add or remove accounts when account
* management is disallowed.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -267,6 +271,9 @@
* primary user or by a profile owner of an organization-owned managed profile on the parent
* profile, it disallows the primary user from changing Wi-Fi access points.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -285,6 +292,9 @@
* When it is set by any of these owners, it applies globally - i.e., it disables airplane mode
* from changing Wi-Fi state.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -306,6 +316,9 @@
* When it is set by any of these owners, it prevents all users from using
* Wi-Fi tethering. Other forms of tethering are not affected.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* This user restriction disables only Wi-Fi tethering.
* Use {@link #DISALLOW_CONFIG_TETHERING} to limit all forms of tethering.
* When {@link #DISALLOW_CONFIG_TETHERING} is set, this user restriction becomes obsolete.
@@ -346,6 +359,9 @@
* sharing Wi-Fi for networks configured by these owners.
* Other networks not configured by these owners are not affected.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -365,6 +381,9 @@
* When it is set by any of these owners, it prevents all users from using
* Wi-Fi Direct.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -384,6 +403,9 @@
* a new Wi-Fi configuration. This does not limit the owner and carrier's ability
* to add a new configuration.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -398,6 +420,9 @@
* Specifies if a user is disallowed from changing the device
* language. The default value is <code>false</code>.
*
+ * <p>Holders of the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCALE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -411,6 +436,10 @@
* prevents device owners and profile owners installing apps. The default value is
* {@code false}.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -423,6 +452,10 @@
* Specifies if a user is disallowed from uninstalling applications.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -442,6 +475,10 @@
* managed profile on the parent profile, it prevents the primary user from turning on
* location sharing.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -460,6 +497,10 @@
* When it is set by any of these owners, it applies globally - i.e., it disables airplane mode
* on the entire device.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AIRPLANE_MODE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -476,6 +517,10 @@
*
* <p>The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>This user restriction has no effect on managed profiles.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -490,6 +535,10 @@
*
* <p>The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>This user restriction has no effect on managed profiles.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -504,6 +553,10 @@
*
* <p>The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DISPLAY}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>This user restriction has no effect on managed profiles.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -519,6 +572,10 @@
* Unknown sources exclude adb and special apps such as trusted app stores.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -536,6 +593,10 @@
* This restriction can be enabled by the profile owner, in which case all accounts and
* profiles will be affected.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -559,6 +620,10 @@
* primary user or by a profile owner of an organization-owned managed profile on the parent
* profile, it disallows the primary user from configuring bluetooth.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -581,6 +646,10 @@
* profile, it disables the primary user from using bluetooth and configuring bluetooth
* in Settings.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -599,6 +668,10 @@
* profile owner of an organization-owned managed profile on the parent profile, it disables
* the primary user from any outgoing bluetooth sharing.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_BLUETOOTH}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Default is <code>true</code> for managed profiles and false otherwise.
*
* <p>When a device upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it
@@ -626,6 +699,10 @@
* user on the device is able to use file transfer over USB because the UI for file transfer
* is always associated with the primary user.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -640,6 +717,10 @@
* Specifies if a user is disallowed from configuring user
* credentials. The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -654,6 +735,10 @@
* This restriction has no effect on managed profiles.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -692,6 +777,10 @@
* that user only, including starting activities, making service calls, accessing content
* providers, sending broadcasts, installing/uninstalling packages, clearing user data, etc.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -712,6 +801,10 @@
* <p>From Android 12 ({@linkplain android.os.Build.VERSION_CODES#S API level 31}) enforcing
* this restriction clears currently active VPN if it was configured by the user.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_VPN}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -729,6 +822,10 @@
* managed profile on the parent profile, it disallows the primary user from turning location
* on or off.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
@@ -755,6 +852,10 @@
* from configuring date, time and timezone and disables all configuring of date, time and
* timezone in Settings.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_TIME}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -776,6 +877,10 @@
* the parent profile, it disables the primary user from using Tethering and hotspots and
* disables all configuring of Tethering and hotspots in Settings.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
@@ -796,6 +901,10 @@
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can reset the network settings of the device.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -811,6 +920,10 @@
* <p>This restriction has no effect on non-admin users since they cannot factory reset the
* device.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -827,6 +940,10 @@
* <p> When the device is an organization-owned device provisioned with a managed profile,
* this restriction will be set as a base restriction which cannot be removed by any admin.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -858,6 +975,10 @@
* <p>The default value for an unmanaged user is <code>false</code>.
* For users with a device owner set, the default is <code>true</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -876,6 +997,10 @@
* the system enforces app verification across all users on the device. Running in earlier
* Android versions, this restriction affects only the profile that sets it.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -893,6 +1018,10 @@
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from configuring cell broadcasts.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>This restriction has no effect on secondary users and managed profiles since only the
@@ -915,6 +1044,10 @@
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from configuring mobile networks.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>This restriction has no effect on secondary users and managed profiles since only the
@@ -949,6 +1082,10 @@
* {@link DevicePolicyManager#addPersistentPreferredActivity(ComponentName, IntentFilter, ComponentName)}
* to add a default intent handler for a given intent filter.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -966,6 +1103,10 @@
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from mounting physical external media.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -986,6 +1127,10 @@
* organization-owned managed profile on the parent profile, it will disallow the primary user
* from adjusting the microphone volume.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MICROPHONE}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -1004,6 +1149,10 @@
* <p>When the restriction is set by profile owners, then it only applies to relevant
* profiles.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>This restriction has no effect on managed profiles.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -1022,6 +1171,10 @@
* primary user or by a profile owner of an organization-owned managed profile on the parent
* profile, it disallows the primary user from making outgoing phone calls.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CALLS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -1041,6 +1194,10 @@
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from sending or receiving SMS messages.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SMS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -1056,6 +1213,10 @@
* device owner may wish to prevent the user from experiencing amusement or
* joy while using the device. The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FUN}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1073,6 +1234,10 @@
* <p>This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WINDOWS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1091,6 +1256,10 @@
* the profile owner of the primary user or a secondary user, the restriction affects only the
* calling user. This user restriction has no effect on managed profiles.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1108,6 +1277,10 @@
* optical character recognition (OCR), we strongly recommend combining this user restriction
* with {@link DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1120,6 +1293,10 @@
* Specifies if the user is not allowed to use NFC to beam out data from apps.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1145,6 +1322,10 @@
* are able to set wallpaper regardless of this restriction.
* The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WALLPAPER}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1163,6 +1344,10 @@
* the parent profile, it disables the primary user from rebooting the device into safe
* boot mode.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SAFE_BOOT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -1191,6 +1376,10 @@
*
* <p>This restriction can be set by device owners and profile owners.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -1208,6 +1397,10 @@
* profile owner of an organization-owned managed profile on the parent profile, it disables
* the primary user from using camera.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1220,6 +1413,10 @@
/**
* Specifies if a user is not allowed to unmute the device's global volume.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* @see DevicePolicyManager#setMasterVolumeMuted(ComponentName, boolean)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -1236,6 +1433,10 @@
* on the primary user or by a profile owner of an organization-owned managed profile on
* the parent profile, it disables the primary user from using cellular data when roaming.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1249,6 +1450,10 @@
* can set this restriction. When it is set by device owner, only the target user will be
* affected. The default value is <code>false</code>.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1285,6 +1490,11 @@
*
* <p>Can be set by profile owners. It only has effect on managed profiles when set by managed
* profile owner. Has no effect on non-managed profiles or users.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1302,7 +1512,12 @@
* {@link android.content.Intent#ACTION_VIEW},
* category {@link android.content.Intent#CATEGORY_BROWSABLE}, scheme http or https, and which
* define a host can handle intents from the managed profile.
- * The default value is <code>false</code>.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILES}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -1319,6 +1534,10 @@
* <p>Device owner and profile owner can set this restriction. When it is set by device owner,
* only the target user will be affected.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_AUTOFILL}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1336,6 +1555,10 @@
* managed profile on the parent profile, it disables the primary user's screen from being
* captured for artificial intelligence purposes.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CONTENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1353,6 +1576,10 @@
* managed profile on the parent profile, it disables the primary user from receiving content
* suggestions for selections based on the contents of their screen.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CONTENT}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1369,6 +1596,10 @@
* {@link DevicePolicyManager#switchUser(ComponentName, UserHandle)} when this restriction is
* set.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1391,7 +1622,12 @@
* This restriction is only meaningful when set by profile owner. When it is set by device
* owner, it does not have any effect.
* <p>
- * The default value is <code>false</code>.
+ *
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
+ * <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1404,6 +1640,10 @@
*
* This restriction can be set by device or profile owner.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PRINTING}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* The default value is {@code false}.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1419,6 +1659,10 @@
* organization-owned managed profile on the parent profile. When it is set by either of these
* owners, it applies globally.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -1525,6 +1769,10 @@
* In all cases, the setting applies globally on the device and will prevent the device from
* scanning for or connecting to 2g networks, except in the case of an emergency.
*
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MOBILE_NETWORK}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
+ *
* <p>The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1537,15 +1785,19 @@
* This user restriction specifies if Ultra-wideband is disallowed on the device. If
* Ultra-wideband is disallowed it cannot be turned on via Settings.
*
+ * <p>
+ * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
+ * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+ *
* <p>This restriction can only be set by a device owner or a profile owner of an
* organization-owned managed profile on the parent profile.
* In both cases, the restriction applies globally on the device and will turn off the
* ultra-wideband radio if it's currently on and prevent the radio from being turned on in
* the future.
*
- * <p>
- * Ultra-wideband (UWB) is a radio technology that can use a very low energy level
- * for short-range, high-bandwidth communications over a large portion of the radio spectrum.
+ * <p>Holders of the permission
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}
+ * can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Default is <code>false</code>.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a42af1a..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) {
@@ -12446,6 +12448,17 @@
"bypass_device_policy_management_role_qualifications";
/**
+ * Whether work profile telephony feature is enabled for non
+ * {@link android.app.role.RoleManager#ROLE_DEVICE_POLICY_MANAGEMENT} holders.
+ * ("0" = false, "1" = true).
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS =
+ "allow_work_profile_telephony_for_non_dpm_role_holders";
+
+ /**
* Indicates whether mobile data should be allowed while the device is being provisioned.
* This allows the provisioning process to turn off mobile data before the user
* has an opportunity to set things up, preventing other processes from burning
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/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 54d19eb..ee6ac12 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -50,6 +50,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -205,7 +206,7 @@
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
- tn.mNextView = mNextView;
+ tn.mNextView = new WeakReference<>(mNextView);
final boolean isUiContext = mContext.isUiContext();
final int displayId = mContext.getDisplayId();
@@ -622,7 +623,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
View mView;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mNextView;
+ WeakReference<View> mNextView;
int mDuration;
WindowManager mWM;
@@ -632,7 +633,7 @@
private final ToastPresenter mPresenter;
@GuardedBy("mCallbacks")
- private final List<Callback> mCallbacks;
+ private final WeakReference<List<Callback>> mCallbacks;
/**
* Creates a {@link ITransientNotification} object.
@@ -649,7 +650,7 @@
mParams = mPresenter.getLayoutParams();
mPackageName = packageName;
mToken = token;
- mCallbacks = callbacks;
+ mCallbacks = new WeakReference<>(callbacks);
mHandler = new Handler(looper, null) {
@Override
@@ -685,7 +686,11 @@
private List<Callback> getCallbacks() {
synchronized (mCallbacks) {
- return new ArrayList<>(mCallbacks);
+ if (mCallbacks.get() != null) {
+ return new ArrayList<>(mCallbacks.get());
+ } else {
+ return new ArrayList<>();
+ }
}
}
@@ -721,13 +726,15 @@
if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
return;
}
- if (mView != mNextView) {
+ if (mNextView != null && mView != mNextView.get()) {
// remove the old view if necessary
handleHide();
- mView = mNextView;
- mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
- mHorizontalMargin, mVerticalMargin,
- new CallbackBinder(getCallbacks(), mHandler));
+ mView = mNextView.get();
+ if (mView != null) {
+ mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY,
+ mHorizontalMargin, mVerticalMargin,
+ new CallbackBinder(getCallbacks(), mHandler));
+ }
}
}
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 7467100..7cb61fe 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -41,6 +41,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import java.lang.ref.WeakReference;
+
/**
* Class responsible for toast presentation inside app's process and in system UI.
*
@@ -87,25 +89,33 @@
return view;
}
- private final Context mContext;
private final Resources mResources;
- private final WindowManager mWindowManager;
- private final IAccessibilityManager mAccessibilityManager;
+ private final WeakReference<WindowManager> mWindowManager;
+ private final WeakReference<AccessibilityManager> mAccessibilityManager;
private final INotificationManager mNotificationManager;
private final String mPackageName;
+ private final String mContextPackageName;
private final WindowManager.LayoutParams mParams;
@Nullable private View mView;
@Nullable private IBinder mToken;
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
- mContext = context;
mResources = context.getResources();
- mWindowManager = context.getSystemService(WindowManager.class);
+ mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
mNotificationManager = notificationManager;
mPackageName = packageName;
- mAccessibilityManager = accessibilityManager;
+ mContextPackageName = context.getPackageName();
mParams = createLayoutParams();
+
+ // We obtain AccessibilityManager manually via its constructor instead of using method
+ // AccessibilityManager.getInstance() for 2 reasons:
+ // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+ // 2. getInstance() caches the instance for the process even if we pass a different
+ // context to it. This is problematic for multi-user because callers can pass a context
+ // created via Context.createContextAsUser().
+ mAccessibilityManager = new WeakReference<>(
+ new AccessibilityManager(context, accessibilityManager, context.getUserId()));
}
public String getPackageName() {
@@ -173,7 +183,7 @@
params.y = yOffset;
params.horizontalMargin = horizontalMargin;
params.verticalMargin = verticalMargin;
- params.packageName = mContext.getPackageName();
+ params.packageName = mContextPackageName;
params.hideTimeoutMilliseconds =
(duration == Toast.LENGTH_LONG) ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
params.token = windowToken;
@@ -270,8 +280,9 @@
public void hide(@Nullable ITransientNotificationCallback callback) {
checkState(mView != null, "No toast to hide.");
- if (mView.getParent() != null) {
- mWindowManager.removeViewImmediate(mView);
+ final WindowManager windowManager = mWindowManager.get();
+ if (mView.getParent() != null && windowManager != null) {
+ windowManager.removeViewImmediate(mView);
}
try {
mNotificationManager.finishToken(mPackageName, mToken);
@@ -295,14 +306,11 @@
* enabled.
*/
public void trySendAccessibilityEvent(View view, String packageName) {
- // We obtain AccessibilityManager manually via its constructor instead of using method
- // AccessibilityManager.getInstance() for 2 reasons:
- // 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
- // 2. getInstance() caches the instance for the process even if we pass a different
- // context to it. This is problematic for multi-user because callers can pass a context
- // created via Context.createContextAsUser().
- final AccessibilityManager accessibilityManager =
- new AccessibilityManager(mContext, mAccessibilityManager, mContext.getUserId());
+ final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
+ if (accessibilityManager == null) {
+ return;
+ }
+
if (!accessibilityManager.isEnabled()) {
accessibilityManager.removeClient();
return;
@@ -320,11 +328,15 @@
}
private void addToastView() {
+ final WindowManager windowManager = mWindowManager.get();
+ if (windowManager == null) {
+ return;
+ }
if (mView.getParent() != null) {
- mWindowManager.removeView(mView);
+ windowManager.removeView(mView);
}
try {
- mWindowManager.addView(mView, mParams);
+ windowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
// Since the notification manager service cancels the token right after it notifies us
// to cancel the toast there is an inherent race and we may attempt to add a window
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 618670a..e58c044 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -43,7 +43,7 @@
private ProgressCallback mCallback;
private float mProgress = 0;
private BackMotionEvent mLastBackEvent;
- private boolean mStarted = false;
+ private boolean mBackAnimationInProgress = false;
private void setProgress(float progress) {
mProgress = progress;
@@ -87,7 +87,7 @@
* @param event the {@link BackMotionEvent} containing the latest target progress.
*/
public void onBackProgressed(BackMotionEvent event) {
- if (!mStarted) {
+ if (!mBackAnimationInProgress) {
return;
}
mLastBackEvent = event;
@@ -108,7 +108,7 @@
reset();
mLastBackEvent = event;
mCallback = callback;
- mStarted = true;
+ mBackAnimationInProgress = true;
}
/**
@@ -122,7 +122,7 @@
// Should never happen.
mSpring.cancel();
}
- mStarted = false;
+ mBackAnimationInProgress = false;
mLastBackEvent = null;
mCallback = null;
mProgress = 0;
@@ -149,8 +149,13 @@
mSpring.animateToFinalPosition(0);
}
+ /** Returns true if the back animation is in progress. */
+ boolean isBackAnimationInProgress() {
+ return mBackAnimationInProgress;
+ }
+
private void updateProgressValue(float progress) {
- if (mLastBackEvent == null || mCallback == null || !mStarted) {
+ if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) {
return;
}
mCallback.onProgressUpdate(
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 4d0132e..22b2ec0 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -158,6 +158,16 @@
mAllCallbacks.remove(callback);
// Re-populate the top callback to WM if the removed callback was previously the top one.
if (previousTopCallback == callback) {
+ // We should call onBackCancelled() when an active callback is removed from dispatcher.
+ if (mProgressAnimator.isBackAnimationInProgress()
+ && callback instanceof OnBackAnimationCallback) {
+ // The ProgressAnimator will handle the new topCallback, so we don't want to call
+ // onBackCancelled() on it. We call immediately the callback instead.
+ OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
+ animatedCallback.onBackCancelled();
+ Log.d(TAG, "The callback was removed while a back animation was in progress, "
+ + "an onBackCancelled() was dispatched.");
+ }
setTopOnBackInvokedCallback(getTopCallback());
}
}
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/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index db65cb3..7452daa 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -33,6 +33,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
@@ -230,7 +231,15 @@
((Button) findViewById(R.id.button_open)).setText(getOpenInWorkButtonString(launchIntent));
findViewById(R.id.button_open).setOnClickListener(v -> {
- startActivityAsCaller(launchIntent, targetUserId);
+ startActivityAsCaller(
+ launchIntent,
+ ActivityOptions.makeCustomAnimation(
+ getApplicationContext(),
+ R.anim.activity_open_enter,
+ R.anim.push_down_out)
+ .toBundle(),
+ /* ignoreTargetSecurity= */ false,
+ targetUserId);
finish();
});
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 af08e03..2f9f6ae 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -863,6 +863,7 @@
android:label="@string/permlab_readContacts"
android:description="@string/permdesc_readContacts"
android:protectionLevel="dangerous" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- Allows an application to write the user's contacts data.
<p>Protection level: dangerous
@@ -6039,6 +6040,7 @@
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.CALL_PRIVILEGED"
android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
<!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide -->
<permission android:name="android.permission.PERFORM_CDMA_PROVISIONING"
@@ -7690,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"
@@ -8231,7 +8217,7 @@
</service>
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
- android:permission="android.permission.BIND_CONNECTION_SERVICE"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.ConnectionService"/>
diff --git a/core/res/res/layout/autofill_fill_dialog.xml b/core/res/res/layout/autofill_fill_dialog.xml
index 2e65800..d1a4935 100644
--- a/core/res/res/layout/autofill_fill_dialog.xml
+++ b/core/res/res/layout/autofill_fill_dialog.xml
@@ -35,10 +35,10 @@
<ImageView
android:id="@+id/autofill_service_icon"
- android:scaleType="fitStart"
+ android:scaleType="fitCenter"
android:visibility="gone"
- android:layout_width="@dimen/autofill_dialog_icon_size"
- android:layout_height="@dimen/autofill_dialog_icon_size"/>
+ android:layout_height="@dimen/autofill_dialog_icon_max_height"
+ android:layout_width="fill_parent"/>
<LinearLayout
android:id="@+id/autofill_dialog_header"
diff --git a/core/res/res/layout/autofill_save.xml b/core/res/res/layout/autofill_save.xml
index 3c0b789..85529d6 100644
--- a/core/res/res/layout/autofill_save.xml
+++ b/core/res/res/layout/autofill_save.xml
@@ -40,10 +40,10 @@
<ImageView
android:id="@+id/autofill_save_icon"
- android:scaleType="fitStart"
+ android:scaleType="fitCenter"
android:layout_gravity="center"
- android:layout_width="@dimen/autofill_save_icon_size"
- android:layout_height="@dimen/autofill_save_icon_size"/>
+ android:layout_height="@dimen/autofill_save_icon_max_height"
+ android:layout_width="fill_parent"/>
<TextView
android:id="@+id/autofill_save_title"
@@ -60,7 +60,6 @@
android:id="@+id/autofill_save_custom_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/autofill_save_scroll_view_top_margin"
android:visibility="gone"/>
</LinearLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index ba92360..b7d088b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1797,12 +1797,6 @@
-->
<attr name="attributionTags" format="string" />
- <!-- Default value <code>true</code> allows an installer to enable update
- ownership enforcement for this package via {@link
- android.content.pm.PackageInstaller.SessionParams#setRequestUpdateOwnership}
- during initial installation. This overrides the installer's use of {@link
- android.content.pm.PackageInstaller.SessionParams#setRequestUpdateOwnership}.
- -->
<attr name="allowUpdateOwnership" format="boolean" />
<!-- The <code>manifest</code> tag is the root of an
@@ -1842,7 +1836,6 @@
<attr name="isSplitRequired" />
<attr name="requiredSplitTypes" />
<attr name="splitTypes" />
- <attr name="allowUpdateOwnership" />
</declare-styleable>
<!-- The <code>application</code> tag describes application-level components
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8899785..00f8db0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3718,6 +3718,10 @@
magnification settings and adjust the default magnification capability. -->
<bool name="config_magnification_area">true</bool>
+ <!-- The default value for always on magnification feature flag if the remote feature
+ flag does not exist -->
+ <bool name="config_magnification_always_on_enabled">true</bool>
+
<!-- If true, the display will be shifted around in ambient mode. -->
<bool name="config_enableBurnInProtection">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bc0af12..24da59a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -874,6 +874,7 @@
<dimen name="autofill_elevation">32dp</dimen>
<dimen name="autofill_save_inner_padding">16dp</dimen>
<dimen name="autofill_save_icon_size">32dp</dimen>
+ <dimen name="autofill_save_icon_max_height">56dp</dimen>
<dimen name="autofill_save_title_start_padding">8dp</dimen>
<dimen name="autofill_save_scroll_view_top_margin">16dp</dimen>
<dimen name="autofill_save_button_bar_padding">16dp</dimen>
@@ -882,19 +883,18 @@
<!-- Max height of the the autofill save custom subtitle as a fraction of the screen width/height -->
<dimen name="autofill_save_custom_subtitle_max_height">20%</dimen>
- <!-- Max (absolute) dimensions (both width and height) of autofill service icon on autofill save affordance.
- NOTE: the actual displayed size might is actually smaller than this and is hardcoded in the
- autofill_save.xml layout; this dimension is just used to avoid a crash in the UI (if the icon provided
- by the autofill service metadata is bigger than these dimentionsit will not be displayed).
- -->
- <dimen name="autofill_save_icon_max_size">300dp</dimen>
-
<!-- Maximum number of datasets that are visible in the UX picker without scrolling -->
<integer name="autofill_max_visible_datasets">3</integer>
- <!-- Size of an icon in the Autolfill fill dialog -->
+ <!-- Size of an icon in the Autofill fill dialog -->
<dimen name="autofill_dialog_icon_size">32dp</dimen>
+ <!-- The max height of an icon in the Autofill fill dialog. -->
+ <dimen name="autofill_dialog_icon_max_height">56dp</dimen>
+
+ <!-- The max width of the Autofill fill dialog. -->
+ <dimen name="autofill_dialog_max_width">640dp</dimen>
+
<!-- Size of a slice shortcut view -->
<dimen name="slice_shortcut_size">56dp</dimen>
<!-- Size of action icons in a slice -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1e9e8dd..f35e32b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3660,8 +3660,8 @@
<java-symbol type="dimen" name="autofill_dataset_picker_max_width"/>
<java-symbol type="dimen" name="autofill_dataset_picker_max_height"/>
<java-symbol type="dimen" name="autofill_save_custom_subtitle_max_height"/>
- <java-symbol type="dimen" name="autofill_save_icon_max_size"/>
<java-symbol type="integer" name="autofill_max_visible_datasets" />
+ <java-symbol type="dimen" name="autofill_dialog_max_width" />
<java-symbol type="style" name="Theme.DeviceDefault.Autofill" />
<java-symbol type="style" name="Theme.DeviceDefault.Light.Autofill" />
@@ -4536,6 +4536,7 @@
<java-symbol type="string" name="dismiss_action" />
<java-symbol type="bool" name="config_magnification_area" />
+ <java-symbol type="bool" name="config_magnification_always_on_enabled" />
<java-symbol type="bool" name="config_trackerAppNeedsPermissions"/>
<!-- FullScreenMagnification thumbnail -->
@@ -5017,7 +5018,7 @@
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
<java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
-
+
<java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
<java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
<java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerMonitorWrapperTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerMonitorWrapperTest.java
new file mode 100644
index 0000000..1f5e0cf
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerMonitorWrapperTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.app.backup;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupManagerMonitorWrapperTest {
+ @Mock
+ private BackupManagerMonitor mMonitor;
+ private BackupManagerMonitorWrapper mMonitorWrapper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testOnEvent_propagatesToMonitor() throws Exception {
+ mMonitorWrapper = new BackupManagerMonitorWrapper(mMonitor);
+ Bundle eventBundle = new Bundle();
+
+ mMonitorWrapper.onEvent(eventBundle);
+
+ verify(mMonitor, times(/* wantedNumberOfInvocations */ 1)).onEvent(eq(eventBundle));
+ }
+
+ @Test
+ public void testOnEvent_nullMonitor_eventIsIgnored() throws Exception {
+ mMonitorWrapper = new BackupManagerMonitorWrapper(/* monitor */ null);
+
+ mMonitorWrapper.onEvent(new Bundle());
+
+ verify(mMonitor, never()).onEvent(any());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 963014e..4672226 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
@@ -34,9 +33,6 @@
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
@@ -91,19 +87,6 @@
}
@Test
- public void clone_doesNotCopyBitmap() {
- RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
- Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
-
- original.setImageViewBitmap(R.id.image, bitmap);
- RemoteViews clone = original.clone();
- View inflated = clone.apply(mContext, mContainer);
-
- Drawable drawable = ((ImageView) inflated.findViewById(R.id.image)).getDrawable();
- assertSame(bitmap, ((BitmapDrawable)drawable).getBitmap());
- }
-
- @Test
public void clone_originalCanStillBeApplied() {
RemoteViews original = new RemoteViews(mPackage, R.layout.remote_views_test);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index cde100c..8e772a2 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -173,4 +173,25 @@
waitForIdle();
verify(mCallback2).onBackStarted(any(BackEvent.class));
}
+
+ @Test
+ public void onUnregisterWhileBackInProgress_callOnBackCancelled() throws RemoteException {
+ ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+ ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+ mDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+ verify(mWindowSession).setOnBackInvokedCallbackInfo(
+ Mockito.eq(mWindow),
+ captor.capture());
+ IOnBackInvokedCallback iOnBackInvokedCallback = captor.getValue().getCallback();
+ iOnBackInvokedCallback.onBackStarted(mBackEvent);
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ mDispatcher.unregisterOnBackInvokedCallback(mCallback1);
+ verify(mCallback1).onBackCancelled();
+ verifyNoMoreInteractions(mCallback1);
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index ff423c2..96190c4b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -31,12 +31,14 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.WindowExtensions;
import androidx.window.extensions.core.util.function.Consumer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -51,6 +53,7 @@
public class WindowAreaComponentImpl implements WindowAreaComponent,
DeviceStateManager.DeviceStateCallback {
+ private static final int INVALID_DISPLAY_ADDRESS = -1;
private final Object mLock = new Object();
@NonNull
@@ -69,8 +72,7 @@
private final int mConcurrentDisplayState;
@NonNull
private final int[] mFoldedDeviceStates;
- @NonNull
- private long mRearDisplayAddress = 0;
+ private long mRearDisplayAddress = INVALID_DISPLAY_ADDRESS;
@WindowAreaSessionState
private int mRearDisplaySessionStatus = WindowAreaComponent.SESSION_STATE_INACTIVE;
@@ -109,10 +111,7 @@
R.integer.config_deviceStateConcurrentRearDisplay);
mDeviceStateManager.registerCallback(mExecutor, this);
- if (mConcurrentDisplayState != INVALID_DEVICE_STATE) {
- mRearDisplayAddress = Long.parseLong(context.getResources().getString(
- R.string.config_rearDisplayPhysicalAddress));
- }
+ mRearDisplayAddress = getRearDisplayAddress(context);
}
/**
@@ -220,6 +219,44 @@
}
/**
+ * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
+ * display was not found in the display list, but we have already computed the
+ * {@link DisplayMetrics} for that display, we return the cached value. If no display has been
+ * found, then we return an empty {@link DisplayMetrics} value.
+ *
+ * TODO(b/267563768): Update with guidance from Display team for missing displays.
+ *
+ * @since {@link WindowExtensions#VENDOR_API_LEVEL_3}
+ */
+ @Override
+ public DisplayMetrics getRearDisplayMetrics() {
+ DisplayMetrics metrics = null;
+
+ // DISPLAY_CATEGORY_REAR displays are only available when you are in the concurrent
+ // display state, so we have to look through all displays to match the address
+ Display[] displays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+ for (int i = 0; i < displays.length; i++) {
+ DisplayAddress.Physical address =
+ (DisplayAddress.Physical) displays[i].getAddress();
+ if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+ metrics = new DisplayMetrics();
+ displays[i].getRealMetrics(metrics);
+ break;
+ }
+ }
+
+ synchronized (mLock) {
+ // Update the rear display metrics with our latest value if one was received
+ if (metrics != null) {
+ mRearDisplayMetrics = metrics;
+ }
+
+ return Objects.requireNonNullElseGet(mRearDisplayMetrics, DisplayMetrics::new);
+ }
+ }
+
+ /**
* Adds a listener interested in receiving updates on the RearDisplayPresentationStatus
* of the device. Because this is being called from the OEM provided
* extensions, the result of the listener will be posted on the executor
@@ -260,8 +297,8 @@
return;
}
@WindowAreaStatus int currentStatus = getCurrentRearDisplayPresentationModeStatus();
- DisplayMetrics metrics =
- currentStatus == STATUS_UNSUPPORTED ? null : getRearDisplayMetrics();
+ DisplayMetrics metrics = currentStatus == STATUS_UNSUPPORTED ? new DisplayMetrics()
+ : getRearDisplayMetrics();
consumer.accept(
new RearDisplayPresentationStatus(currentStatus, metrics));
}
@@ -491,37 +528,10 @@
}
}
- /**
- * Returns the{@link DisplayMetrics} associated with the rear facing display. If the rear facing
- * display was not found in the display list, but we have already computed the
- * {@link DisplayMetrics} for that display, we return the cached value.
- *
- * TODO(b/267563768): Update with guidance from Display team for missing displays.
- *
- * @throws IllegalArgumentException if the display is not found and there is no cached
- * {@link DisplayMetrics} for this display.
- */
- @GuardedBy("mLock")
- private DisplayMetrics getRearDisplayMetrics() {
- Display[] displays = mDisplayManager.getDisplays(
- DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
- for (int i = 0; i < displays.length; i++) {
- DisplayAddress.Physical address =
- (DisplayAddress.Physical) displays[i].getAddress();
- if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
- if (mRearDisplayMetrics == null) {
- mRearDisplayMetrics = new DisplayMetrics();
- }
- displays[i].getRealMetrics(mRearDisplayMetrics);
- return mRearDisplayMetrics;
- }
- }
- if (mRearDisplayMetrics != null) {
- return mRearDisplayMetrics;
- } else {
- throw new IllegalArgumentException(
- "No display found with the provided display address");
- }
+ private long getRearDisplayAddress(Context context) {
+ String address = context.getResources().getString(
+ R.string.config_rearDisplayPhysicalAddress);
+ return address.isEmpty() ? INVALID_DISPLAY_ADDRESS : Long.parseLong(address);
}
@GuardedBy("mLock")
diff --git a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
index 49491a7..69b339a 100644
--- a/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/reachability_ui_layout.xml
@@ -20,47 +20,48 @@
android:focusable="false"
android:focusableInTouchMode="false"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:theme="@style/ReachabilityEduHandLayout">
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|top"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_up_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_vertical|right"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_right_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_left_hand"
android:layout_gravity="center_vertical|left"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_left_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
<androidx.appcompat.widget.AppCompatTextView
- style="@style/ReachabilityEduHandLayout"
android:text="@string/letterbox_reachability_reposition_text"
app:drawableTopCompat="@drawable/reachability_education_ic_right_hand"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="@dimen/letterbox_reachability_education_dialog_margin"
android:id="@+id/reachability_move_down_button"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:visibility="invisible"/>
</com.android.wm.shell.compatui.ReachabilityEduLayout>
diff --git a/libs/WindowManager/Shell/res/values-night/styles.xml b/libs/WindowManager/Shell/res/values-night/styles.xml
index 758c99d..4871f7a 100644
--- a/libs/WindowManager/Shell/res/values-night/styles.xml
+++ b/libs/WindowManager/Shell/res/values-night/styles.xml
@@ -17,12 +17,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="ReachabilityEduHandLayout">
+ <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat">
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:background">@android:color/transparent</item>
- <item name="android:contentDescription">@string/restart_button_description</item>
- <item name="android:visibility">invisible</item>
<item name="android:lineSpacingExtra">-1sp</item>
<item name="android:textSize">12sp</item>
<item name="android:textAlignment">center</item>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 2b38888..8635c56 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -151,12 +151,10 @@
</item>
</style>
- <style name="ReachabilityEduHandLayout">
+ <style name="ReachabilityEduHandLayout" parent="Theme.AppCompat.Light">
<item name="android:focusable">false</item>
<item name="android:focusableInTouchMode">false</item>
<item name="android:background">@android:color/transparent</item>
- <item name="android:contentDescription">@string/restart_button_description</item>
- <item name="android:visibility">invisible</item>
<item name="android:lineSpacingExtra">-1sp</item>
<item name="android:textSize">12sp</item>
<item name="android:textAlignment">center</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 3eb9fa2..6986810 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -17,12 +17,18 @@
package com.android.wm.shell.bubbles;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_DENSITY;
+import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
+import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
@@ -47,6 +53,7 @@
import android.app.NotificationChannel;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -74,7 +81,6 @@
import android.util.SparseArray;
import android.view.IWindowManager;
import android.view.SurfaceControl;
-import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
@@ -102,6 +108,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
@@ -109,7 +116,6 @@
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
-import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
@@ -135,7 +141,7 @@
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener,
+public class BubbleController implements ComponentCallbacks2,
RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -153,7 +159,6 @@
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
-
/**
* Common interface to send updates to bubble views.
*/
@@ -237,17 +242,17 @@
/** Whether or not the BubbleStackView has been added to the WindowManager. */
private boolean mAddedToWindowManager = false;
- /** Saved screen density, used to detect display size changes in {@link #onConfigChanged}. */
- private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+ /**
+ * Saved configuration, used to detect changes in
+ * {@link #onConfigurationChanged(Configuration)}
+ */
+ private final Configuration mLastConfiguration = new Configuration();
- /** Saved screen bounds, used to detect screen size changes in {@link #onConfigChanged}. **/
- private Rect mScreenBounds = new Rect();
-
- /** Saved font scale, used to detect font size changes in {@link #onConfigChanged}. */
- private float mFontScale = 0;
-
- /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */
- private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
+ /**
+ * Saved screen bounds, used to detect screen size changes in
+ * {@link #onConfigurationChanged(Configuration)}.
+ */
+ private final Rect mScreenBounds = new Rect();
/** Saved insets, used to detect WindowInset changes. */
private WindowInsets mWindowInsets;
@@ -293,7 +298,8 @@
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService) {
- mContext = context;
+ mContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
+ mLastConfiguration.setTo(mContext.getResources().getConfiguration());
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
mLauncherApps = launcherApps;
@@ -317,11 +323,11 @@
mBubblePositioner = positioner;
mBubbleData = data;
mSavedUserBubbleData = new SparseArray<>();
- mBubbleIconFactory = new BubbleIconFactory(context,
- context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
- context.getResources().getDimensionPixelSize(
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
mTaskViewTransitions = taskViewTransitions;
@@ -482,7 +488,6 @@
}
mCurrentProfiles = userProfiles;
- mShellController.addConfigurationChangeListener(this);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
@@ -774,6 +779,7 @@
try {
mAddedToWindowManager = true;
registerBroadcastReceiver();
+ mContext.registerComponentCallbacks(this);
mBubbleData.getOverflow().initialize(this);
// (TODO: b/273314541) some duplication in the inset listener
if (isShowingAsBubbleBar()) {
@@ -831,6 +837,7 @@
// Put on background for this binder call, was causing jank
mBackgroundExecutor.execute(() -> {
try {
+ mContext.unregisterComponentCallbacks(this);
mContext.unregisterReceiver(mBroadcastReceiver);
} catch (IllegalArgumentException e) {
// Not sure if this happens in production, but was happening in tests
@@ -930,8 +937,7 @@
mSavedUserBubbleData.remove(userId);
}
- @Override
- public void onThemeChanged() {
+ private void onThemeChanged() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
@@ -963,34 +969,60 @@
}
}
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (mBubblePositioner != null) {
- mBubblePositioner.update();
- }
- if (mStackView != null && newConfig != null) {
- if (newConfig.densityDpi != mDensityDpi
- || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
- mDensityDpi = newConfig.densityDpi;
- mScreenBounds.set(newConfig.windowConfiguration.getBounds());
- mBubbleData.onMaxBubblesChanged();
- mBubbleIconFactory = new BubbleIconFactory(mContext,
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
- mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
- mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width));
- mStackView.onDisplaySizeChanged();
+ mMainExecutor.execute(() -> {
+ final int diff = newConfig.diff(mLastConfiguration);
+ final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+ || (diff & CONFIG_UI_MODE) != 0;
+ if (themeChanged) {
+ onThemeChanged();
}
- if (newConfig.fontScale != mFontScale) {
- mFontScale = newConfig.fontScale;
- mStackView.updateFontScale();
+ if (mBubblePositioner != null) {
+ mBubblePositioner.update();
}
- if (newConfig.getLayoutDirection() != mLayoutDirection) {
- mLayoutDirection = newConfig.getLayoutDirection();
- mStackView.onLayoutDirectionChanged(mLayoutDirection);
+ if (mStackView != null) {
+ final boolean densityChanged = (diff & CONFIG_DENSITY) != 0;
+ final boolean fontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0;
+ final boolean layoutDirectionChanged = (diff & CONFIG_LAYOUT_DIRECTION) != 0;
+ if (densityChanged
+ || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) {
+ mScreenBounds.set(newConfig.windowConfiguration.getBounds());
+ mBubbleData.onMaxBubblesChanged();
+ mBubbleIconFactory = new BubbleIconFactory(mContext,
+ mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_badge_size),
+ mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width));
+ mStackView.onDisplaySizeChanged();
+ }
+ if (fontScaleChanged) {
+ mStackView.updateFontScale();
+ }
+ if (layoutDirectionChanged) {
+ mStackView.onLayoutDirectionChanged(newConfig.getLayoutDirection());
+ }
}
- }
+ mLastConfiguration.setTo(newConfig);
+ });
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onTrimMemory(int level) {
+ // Do nothing
+ }
+
+ // Note: Component callbacks are always called on the main thread of the process
+ @ExternalMainThread
+ @Override
+ public void onLowMemory() {
+ // Do nothing
}
private void onNotificationPanelExpandedChanged(boolean expanded) {
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..753dfa7 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
@@ -22,6 +22,8 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -51,6 +53,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.
*/
@@ -58,7 +62,6 @@
private static final String TAG = SplitDecorManager.class.getSimpleName();
private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
private static final String GAP_BACKGROUND_SURFACE_NAME = "GapBackground";
- private static final long FADE_DURATION = 133;
private final IconProvider mIconProvider;
private final SurfaceSession mSurfaceSession;
@@ -251,7 +254,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();
}
@@ -261,6 +264,7 @@
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mScreenshotAnimator = ValueAnimator.ofFloat(1, 0);
+ mScreenshotAnimator.setDuration(FADE_DURATION);
mScreenshotAnimator.addUpdateListener(valueAnimator -> {
final float progress = (float) valueAnimator.getAnimatedValue();
animT.setAlpha(mScreenshot, progress);
@@ -281,7 +285,7 @@
mScreenshot = null;
if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
- animFinishedCallback.run();
+ animFinishedCallback.accept(true);
}
}
});
@@ -313,12 +317,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/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 0bcafe5..ef93a33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -43,6 +43,11 @@
*/
public static final int SPLIT_POSITION_BOTTOM_OR_RIGHT = 1;
+ /**
+ * Duration used for every split fade-in or fade-out.
+ */
+ public static final int FADE_DURATION = 133;
+
@IntDef(prefix = {"SPLIT_POSITION_"}, value = {
SPLIT_POSITION_UNDEFINED,
SPLIT_POSITION_TOP_OR_LEFT,
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/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 8709eab..58bc81d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -64,7 +64,6 @@
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.util.Log;
import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
@@ -109,7 +108,6 @@
public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
- private static final boolean DEBUG = false;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -1045,7 +1043,8 @@
void onExitPipFinished(TaskInfo info) {
if (mLeash == null) {
// TODO(239461594): Remove once the double call to onExitPipFinished() is fixed
- Log.w(TAG, "Warning, onExitPipFinished() called multiple times in the same sessino");
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Warning, onExitPipFinished() called multiple times in the same session");
return;
}
@@ -1134,15 +1133,13 @@
&& (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP);
if ((mPipTransitionState.getInSwipePipToHomeTransition()
|| waitForFixedRotationOnEnteringPip) && fromRotation) {
- if (DEBUG) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Skip onMovementBoundsChanged on rotation change"
- + " InSwipePipToHomeTransition=%b"
- + " mWaitForFixedRotation=%b"
- + " getTransitionState=%d", TAG,
- mPipTransitionState.getInSwipePipToHomeTransition(), mWaitForFixedRotation,
- mPipTransitionState.getTransitionState());
- }
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Skip onMovementBoundsChanged on rotation change"
+ + " InSwipePipToHomeTransition=%b"
+ + " mWaitForFixedRotation=%b"
+ + " getTransitionState=%d", TAG,
+ mPipTransitionState.getInSwipePipToHomeTransition(), mWaitForFixedRotation,
+ mPipTransitionState.getTransitionState());
return;
}
final PipAnimationController.PipTransitionAnimator animator =
@@ -1437,8 +1434,9 @@
}
if (mLeash == null || !mLeash.isValid()) {
- Log.e(TAG, String.format("scheduleFinishResizePip with null leash! mState=%d",
- mPipTransitionState.getTransitionState()));
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: scheduleFinishResizePip with null leash! mState=%d",
+ TAG, mPipTransitionState.getTransitionState());
return;
}
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/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index ef5e501..2a61445 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -42,7 +42,8 @@
"ShellBackPreview"),
WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ // TODO(b/282232877): turn logToLogcat to false.
+ WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SHELL),
WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM_SPLIT_SCREEN),
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..38c420a 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
@@ -22,6 +22,7 @@
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -34,7 +35,6 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -63,7 +63,7 @@
private final Runnable mOnFinish;
DismissSession mPendingDismiss = null;
- TransitSession mPendingEnter = null;
+ EnterSession mPendingEnter = null;
TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
@@ -120,6 +120,7 @@
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
// Play some place-holder fade animations
+ final boolean isEnter = isPendingEnter(transition);
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
@@ -142,17 +143,23 @@
change.getEndRelOffset().x, change.getEndRelOffset().y);
}
}
- boolean isRootOrSplitSideRoot = change.getParent() == null
- || topRoot.equals(change.getParent());
- boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
- // For enter or exit, we only want to animate side roots and the divider but not the
- // top-root.
- if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
- continue;
- }
- if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
- || sideRoot.equals(change.getContainer()))) {
+ final boolean isTopRoot = topRoot.equals(change.getContainer());
+ final boolean isMainRoot = mainRoot.equals(change.getContainer());
+ final boolean isSideRoot = sideRoot.equals(change.getContainer());
+ final boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+ final boolean isMainChild = mainRoot.equals(change.getParent());
+ final boolean isSideChild = sideRoot.equals(change.getParent());
+ if (isEnter && (isMainChild || isSideChild)) {
+ // Reset child tasks bounds on finish.
+ mFinishTransaction.setPosition(leash,
+ change.getEndRelOffset().x, change.getEndRelOffset().y);
+ mFinishTransaction.setCrop(leash, null);
+ } else if (isEnter && isTopRoot) {
+ // Ensure top root is visible at start.
+ t.setAlpha(leash, 1.f);
+ t.show(leash);
+ } else if (isEnter && isMainRoot || isSideRoot) {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
@@ -161,10 +168,24 @@
t.setLayer(leash, Integer.MAX_VALUE);
t.show(leash);
}
+ // These container changes we don't want to animate them.
+ // We should only animate stage root, divider and child tasks are not under stage root.
+ if (isTopRoot || isMainChild || isSideChild || change.getTaskInfo() == null) {
+ continue;
+ }
+
+ if (isEnter && mPendingEnter.mResizeAnim) {
+ // We will run animation in next transition so skip anim here
+ continue;
+ } else if (isEnter && isMainRoot) {
+ // Main stage already on top so skip fade in animation to reduce flicker.
+ continue;
+ }
+
boolean isOpening = TransitionUtil.isOpeningType(info.getType());
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
// fade in
- startExampleAnimation(leash, true /* show */);
+ startFadeAnimation(leash, true /* show */);
} else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
// fade out
if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
@@ -173,7 +194,7 @@
// and don't animate it so it doesn't pop-in when reparented.
t.setAlpha(leash, 0.f);
} else {
- startExampleAnimation(leash, false /* show */);
+ startFadeAnimation(leash, false /* show */);
}
}
}
@@ -208,11 +229,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 */);
+ });
+ }
});
}
}
@@ -265,7 +288,7 @@
Transitions.TransitionHandler handler,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback,
- int extraTransitType) {
+ int extraTransitType, boolean resizeAnim) {
if (mPendingEnter != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " skip to start enter split transition since it already exist. ");
@@ -273,7 +296,7 @@
}
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback,
- extraTransitType);
+ extraTransitType, resizeAnim);
return transition;
}
@@ -282,9 +305,10 @@
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionConsumedCallback consumedCallback,
@Nullable TransitionFinishedCallback finishedCallback,
- int extraTransitType) {
- mPendingEnter = new TransitSession(
- transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType);
+ int extraTransitType, boolean resizeAnim) {
+ mPendingEnter = new EnterSession(
+ transition, consumedCallback, finishedCallback, remoteTransition, extraTransitType,
+ resizeAnim);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter split screen");
@@ -407,78 +431,27 @@
}
}
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
+ private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) {
final float end = show ? 1.f : 0.f;
final float start = 1.f - end;
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
+ va.setDuration(FADE_DURATION);
va.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
transaction.apply();
});
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish(null /* wct */, null /* wctCB */);
- });
- };
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
- @NonNull Rect startBounds, @NonNull Rect endBounds) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setWindowCrop(leash,
- (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
- (int) (startBounds.height() * (1.f - fraction)
- + endBounds.height() * fraction));
- transaction.setPosition(leash,
- startBounds.left * (1.f - fraction) + endBounds.left * fraction,
- startBounds.top * (1.f - fraction) + endBounds.top * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setWindowCrop(leash, 0, 0);
- transaction.setPosition(leash, endBounds.left, endBounds.top);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish(null /* wct */, null /* wctCB */);
- });
- };
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish(null /* wct */, null /* wctCB */);
+ });
}
});
mAnimations.add(va);
@@ -567,6 +540,21 @@
}
}
+ /** Bundled information of enter transition. */
+ class EnterSession extends TransitSession {
+ final boolean mResizeAnim;
+
+ EnterSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback,
+ @Nullable RemoteTransition remoteTransition,
+ int extraTransitType, boolean resizeAnim) {
+ super(transition, consumedCallback, finishedCallback, remoteTransition,
+ extraTransitType);
+ this.mResizeAnim = resizeAnim;
+ }
+ }
+
/** Bundled information of dismiss transition. */
class DismissSession extends TransitSession {
final int mReason;
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..087e3a2 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
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -394,7 +395,8 @@
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
null, this, null /* consumedCallback */, null /* finishedCallback */,
isSplitScreenVisible()
- ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN,
+ !mIsDropEntering);
} else {
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -502,7 +504,8 @@
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
- null /* consumedCallback */, null /* finishedCallback */, extraTransitType);
+ null /* consumedCallback */, null /* finishedCallback */, extraTransitType,
+ !mIsDropEntering);
}
/** Launches an activity into split by legacy transition. */
@@ -660,7 +663,7 @@
mSplitTransitions.startEnterTransition(
TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -712,7 +715,7 @@
mSplitTransitions.startEnterTransition(
TRANSIT_TO_FRONT, wct, remoteTransition, this, null, null,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
setEnterInstanceId(instanceId);
}
@@ -1493,14 +1496,6 @@
private void prepareBringSplit(WindowContainerTransaction wct,
@Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
- StageTaskListener targetStage;
- if (isSplitScreenVisible()) {
- // If the split screen is foreground, retrieves target stage based on position.
- targetStage = startPosition == mSideStagePosition ? mSideStage : mMainStage;
- } else {
- targetStage = mSideStage;
- }
-
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1509,13 +1504,8 @@
// and evict all tasks current under its.
if (!isSplitScreenVisible()) {
// Recreate so we need to reset position rather than keep position of background split.
- mSplitLayout.resetDividerPosition();
- updateWindowBounds(mSplitLayout, wct);
- final StageTaskListener anotherStage = targetStage == mMainStage
- ? mSideStage : mMainStage;
- anotherStage.reparentTopTask(wct);
- wct.reorder(mRootTaskInfo.token, true);
- setRootForceTranslucent(false, wct);
+ mMainStage.reparentTopTask(wct);
+ prepareSplitLayout(wct);
}
}
@@ -1531,8 +1521,22 @@
mSideStage.addTask(taskInfo, wct);
}
mMainStage.activate(wct, true /* includingTopTask */);
- mSplitLayout.resetDividerPosition();
+ prepareSplitLayout(wct);
+ }
+
+ private void prepareSplitLayout(WindowContainerTransaction wct) {
+ if (mIsDropEntering) {
+ mSplitLayout.resetDividerPosition();
+ } else {
+ mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ }
updateWindowBounds(mSplitLayout, wct);
+ if (!mIsDropEntering) {
+ // Reset its smallest width dp to avoid is change layout before it actually resized to
+ // split bounds.
+ wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
+ SMALLEST_SCREEN_WIDTH_DP_UNDEFINED);
+ }
wct.reorder(mRootTaskInfo.token, true);
setRootForceTranslucent(false, wct);
}
@@ -1547,6 +1551,7 @@
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
t.show(mRootTaskLeash);
setSplitsVisible(true);
+ mIsDropEntering = false;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
@@ -1776,18 +1781,10 @@
// Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
&& mSplitRequest == null) {
- if (mIsDropEntering) {
- mSplitLayout.resetDividerPosition();
- } else {
- mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
- }
final WindowContainerTransaction wct = new WindowContainerTransaction();
- mMainStage.reparentTopTask(wct);
+ prepareEnterSplitScreen(wct);
mMainStage.evictAllChildren(wct);
mSideStage.evictOtherChildren(wct, taskId);
- updateWindowBounds(mSplitLayout, wct);
- wct.reorder(mRootTaskInfo.token, true);
- setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1982,20 +1979,8 @@
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
- mSplitLayout.init();
-
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mIsDropEntering) {
- prepareEnterSplitScreen(wct);
- } else {
- // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
- onSplitScreenEnter();
- mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
- mMainStage.activate(wct, true /* includingTopTask */);
- updateWindowBounds(mSplitLayout, wct);
- wct.reorder(mRootTaskInfo.token, true);
- setRootForceTranslucent(false, wct);
- }
+ prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -2175,6 +2160,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) {
@@ -2344,7 +2337,7 @@
prepareEnterSplitScreen(out);
mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
null /* consumedCallback */, null /* finishedCallback */,
- 0 /* extraTransitType */);
+ 0 /* extraTransitType */, !mIsDropEntering);
}
}
return out;
@@ -2369,6 +2362,10 @@
+ " so make sure split-screen state is cleaned-up. "
+ "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(),
mSideStage.getChildCount());
+ if (triggerTask != null) {
+ mRecentTasks.ifPresent(
+ recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
+ }
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
}
}
@@ -2583,7 +2580,7 @@
}
private boolean startPendingEnterAnimation(
- @NonNull SplitScreenTransitions.TransitSession enterTransition,
+ @NonNull SplitScreenTransitions.EnterSession enterTransition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
// First, verify that we actually have opened apps in both splits.
@@ -2641,7 +2638,6 @@
+ " to have been called with " + sideChild.getTaskInfo().taskId
+ " before startAnimation().");
}
-
final TransitionInfo.Change finalMainChild = mainChild;
final TransitionInfo.Change finalSideChild = sideChild;
enterTransition.setFinishedCallback((callbackWct, callbackT) -> {
@@ -2651,6 +2647,10 @@
if (finalSideChild != null) {
mSideStage.evictOtherChildren(callbackWct, finalSideChild.getTaskInfo().taskId);
}
+ if (enterTransition.mResizeAnim) {
+ mShowDecorImmediately = true;
+ mSplitLayout.flingDividerToCenter();
+ }
});
finishEnterSplitScreen(finishT);
@@ -2749,7 +2749,7 @@
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ && taskInfo.getWindowingMode() != WINDOWING_MODE_MULTI_WINDOW) {
recentTasks.removeSplitPair(taskInfo.taskId);
}
}
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/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index de701ec..8038453 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -182,7 +182,7 @@
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(testRemote, "Test"), mStageCoordinator, null, null,
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -408,7 +408,7 @@
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_OPEN, new WindowContainerTransaction(),
new RemoteTransition(new TestRemoteTransition(), "Test"),
- mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN);
+ mStageCoordinator, null, null, TRANSIT_SPLIT_SCREEN_PAIR_OPEN, false);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 44a0ede..6621ab8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -158,7 +158,6 @@
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
verify(mMainStage).reparentTopTask(eq(wct));
- verify(mSplitLayout).resetDividerPosition();
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
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/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 38d17de..8abcd9a 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -194,7 +194,11 @@
ALOGE("Can not create a codec for Gainmap.");
return false;
}
- SkColorType decodeColorType = codec->computeOutputColorType(kN32_SkColorType);
+ SkColorType decodeColorType = kN32_SkColorType;
+ if (codec->getInfo().colorType() == kGray_8_SkColorType) {
+ decodeColorType = kGray_8_SkColorType;
+ }
+ decodeColorType = codec->computeOutputColorType(decodeColorType);
sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace(decodeColorType, nullptr);
SkISize size = codec->getSampledDimensions(sampleSize);
@@ -217,7 +221,11 @@
const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
alphaType, decodeColorSpace);
- const SkImageInfo& bitmapInfo = decodeInfo;
+ SkImageInfo bitmapInfo = decodeInfo;
+ if (decodeColorType == kGray_8_SkColorType) {
+ // We treat gray8 as alpha8 in Bitmap's API surface
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
SkBitmap decodeBitmap;
sk_sp<Bitmap> nativeBitmap = nullptr;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 597cbf7..814ac4d 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -156,6 +156,7 @@
void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
if (!init()) return;
+ mResetsSinceLastReport = 0;
if (actualDurationNanos > kSanityCheckLowerBound &&
actualDurationNanos < kSanityCheckUpperBound) {
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
@@ -163,9 +164,12 @@
}
void HintSessionWrapper::sendLoadResetHint() {
+ static constexpr int kMaxResetsSinceLastReport = 2;
if (!init()) return;
nsecs_t now = systemTime();
- if (now - mLastFrameNotification > kResetHintTimeout) {
+ if (now - mLastFrameNotification > kResetHintTimeout &&
+ mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
+ ++mResetsSinceLastReport;
gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
}
mLastFrameNotification = now;
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index b7a433f..24b8150 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -42,6 +42,7 @@
APerformanceHintSession* mHintSession = nullptr;
std::future<APerformanceHintSession*> mHintSessionFuture;
+ int mResetsSinceLastReport = 0;
nsecs_t mLastFrameNotification = 0;
nsecs_t mLastTargetWorkDuration = 0;
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/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index f6ca0c4..67c4cdc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -73,6 +73,7 @@
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
) {
+ ActivityTitle(title)
var isSearchMode by rememberSaveable { mutableStateOf(false) }
val viewModel: SearchScaffoldViewModel = viewModel()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index f4e504a..8919402 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,6 +16,9 @@
package com.android.settingslib.spa.widget.scaffold
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -25,8 +28,10 @@
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.compose.verticalValues
@@ -44,6 +49,7 @@
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
+ ActivityTitle(title)
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
@@ -55,6 +61,25 @@
}
}
+/**
+ * Sets a title for the activity.
+ *
+ * So the TalkBack can read out the correct page title.
+ */
+@Composable
+internal fun ActivityTitle(title: String) {
+ val context = LocalContext.current
+ LaunchedEffect(true) {
+ context.getActivity()?.title = title
+ }
+}
+
+private fun Context.getActivity(): Activity? = when (this) {
+ is Activity -> this
+ is ContextWrapper -> baseContext.getActivity()
+ else -> null
+}
+
@Preview
@Composable
private fun SettingsScaffoldPreview() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
index f042404..872d957 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -16,9 +16,12 @@
package com.android.settingslib.spa.widget.scaffold
+import android.app.Activity
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.Text
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -29,12 +32,22 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
@RunWith(AndroidJUnit4::class)
class SettingsScaffoldTest {
@get:Rule
val composeTestRule = createComposeRule()
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var activity: Activity
+
@Test
fun settingsScaffold_titleIsDisplayed() {
composeTestRule.setContent {
@@ -78,7 +91,18 @@
assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
}
+ @Test
+ fun activityTitle() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides activity) {
+ ActivityTitle(title = TITLE)
+ }
+ }
+
+ verify(activity).title = TITLE
+ }
+
private companion object {
- const val TITLE = "title"
+ const val TITLE = "Title"
}
}
diff --git a/packages/SettingsLib/res/drawable/ic_media_car.xml b/packages/SettingsLib/res/drawable/ic_media_car.xml
new file mode 100644
index 0000000..452e7bb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_car.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_computer.xml b/packages/SettingsLib/res/drawable/ic_media_computer.xml
new file mode 100644
index 0000000..2aa6f8e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_computer.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M80,840Q63,840 51.5,828.5Q40,817 40,800Q40,783 51.5,771.5Q63,760 80,760L880,760Q897,760 908.5,771.5Q920,783 920,800Q920,817 908.5,828.5Q897,840 880,840L80,840ZM160,720Q127,720 103.5,696.5Q80,673 80,640L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,640Q880,673 856.5,696.5Q833,720 800,720L160,720ZM160,640L800,640Q800,640 800,640Q800,640 800,640L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640ZM160,640Q160,640 160,640Q160,640 160,640L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640L160,640Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_game_console.xml b/packages/SettingsLib/res/drawable/ic_media_game_console.xml
new file mode 100644
index 0000000..8e422ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_game_console.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M189,800Q129,800 86.5,757Q44,714 42,653Q42,644 43,635Q44,626 46,617L130,281Q144,227 187,193.5Q230,160 285,160L675,160Q730,160 773,193.5Q816,227 830,281L914,617Q916,626 917.5,635.5Q919,645 919,654Q919,715 875.5,757.5Q832,800 771,800Q729,800 693,778Q657,756 639,718L611,660Q606,650 596,645Q586,640 575,640L385,640Q374,640 364,645Q354,650 349,660L321,718Q303,756 267,778Q231,800 189,800ZM192,720Q211,720 226.5,710Q242,700 250,683L278,626Q293,595 322,577.5Q351,560 385,560L575,560Q609,560 638,578Q667,596 683,626L711,683Q719,700 734.5,710Q750,720 769,720Q797,720 817,701.5Q837,683 838,655Q838,656 836,636L752,301Q745,274 724,257Q703,240 675,240L285,240Q257,240 235.5,257Q214,274 208,301L124,636Q122,642 122,654Q122,682 142.5,701Q163,720 192,720ZM540,440Q557,440 568.5,428.5Q580,417 580,400Q580,383 568.5,371.5Q557,360 540,360Q523,360 511.5,371.5Q500,383 500,400Q500,417 511.5,428.5Q523,440 540,440ZM620,360Q637,360 648.5,348.5Q660,337 660,320Q660,303 648.5,291.5Q637,280 620,280Q603,280 591.5,291.5Q580,303 580,320Q580,337 591.5,348.5Q603,360 620,360ZM620,520Q637,520 648.5,508.5Q660,497 660,480Q660,463 648.5,451.5Q637,440 620,440Q603,440 591.5,451.5Q580,463 580,480Q580,497 591.5,508.5Q603,520 620,520ZM700,440Q717,440 728.5,428.5Q740,417 740,400Q740,383 728.5,371.5Q717,360 700,360Q683,360 671.5,371.5Q660,383 660,400Q660,417 671.5,428.5Q683,440 700,440ZM340,500Q353,500 361.5,491.5Q370,483 370,470L370,430L410,430Q423,430 431.5,421.5Q440,413 440,400Q440,387 431.5,378.5Q423,370 410,370L370,370L370,330Q370,317 361.5,308.5Q353,300 340,300Q327,300 318.5,308.5Q310,317 310,330L310,370L270,370Q257,370 248.5,378.5Q240,387 240,400Q240,413 248.5,421.5Q257,430 270,430L310,430L310,470Q310,483 318.5,491.5Q327,500 340,500ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml b/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml
new file mode 100644
index 0000000..9c73485
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_smartwatch.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_media_tablet.xml b/packages/SettingsLib/res/drawable/ic_media_tablet.xml
new file mode 100644
index 0000000..c773b96
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_media_tablet.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M200,920Q167,920 143.5,896.5Q120,873 120,840L120,120Q120,87 143.5,63.5Q167,40 200,40L760,40Q793,40 816.5,63.5Q840,87 840,120L840,840Q840,873 816.5,896.5Q793,920 760,920L200,920ZM200,720L200,840Q200,840 200,840Q200,840 200,840L760,840Q760,840 760,840Q760,840 760,840L760,720L200,720ZM400,800L560,800L560,760L400,760L400,800ZM200,640L760,640L760,240L200,240L200,640ZM200,160L760,160L760,120Q760,120 760,120Q760,120 760,120L200,120Q200,120 200,120Q200,120 200,120L200,160ZM200,160L200,120Q200,120 200,120Q200,120 200,120L200,120Q200,120 200,120Q200,120 200,120L200,160L200,160ZM200,720L200,720L200,840Q200,840 200,840Q200,840 200,840L200,840Q200,840 200,840Q200,840 200,840L200,720Z"/>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 2555e2b..76556639 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -16,12 +16,11 @@
package com.android.settingslib.fuelgauge;
-import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
-import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.os.BatteryManager.EXTRA_HEALTH;
-import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
+import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
import static android.os.BatteryManager.EXTRA_PLUGGED;
@@ -51,7 +50,7 @@
public final int status;
public final int level;
public final int plugged;
- public final int health;
+ public final int chargingStatus;
public final int maxChargingWattage;
public final boolean present;
public final Optional<Boolean> incompatibleCharger;
@@ -62,12 +61,12 @@
? null : new BatteryStatus(batteryChangedIntent, incompatibleCharger);
}
- public BatteryStatus(int status, int level, int plugged, int health,
+ public BatteryStatus(int status, int level, int plugged, int chargingStatus,
int maxChargingWattage, boolean present) {
this.status = status;
this.level = level;
this.plugged = plugged;
- this.health = health;
+ this.chargingStatus = chargingStatus;
this.maxChargingWattage = maxChargingWattage;
this.present = present;
this.incompatibleCharger = Optional.empty();
@@ -86,7 +85,8 @@
status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
level = getBatteryLevel(batteryChangedIntent);
- health = batteryChangedIntent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
+ chargingStatus = batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS,
+ CHARGING_POLICY_DEFAULT);
present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
this.incompatibleCharger = incompatibleCharger;
@@ -143,9 +143,9 @@
return level < LOW_BATTERY_THRESHOLD;
}
- /** Whether battery is overheated. */
- public boolean isOverheated() {
- return health == BATTERY_HEALTH_OVERHEAT;
+ /** Whether battery defender is enabled. */
+ public boolean isBatteryDefender() {
+ return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
}
/** Return current chargin speed is fast, slow or normal. */
@@ -163,7 +163,8 @@
@Override
public String toString() {
return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
- + ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
+ + ",chargingStatus=" + chargingStatus + ",maxChargingWattage=" + maxChargingWattage
+ + "}";
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index c036fdb..632120e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -15,10 +15,14 @@
*/
package com.android.settingslib.media;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK;
import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import android.content.Context;
@@ -31,8 +35,6 @@
import com.android.settingslib.R;
-import java.util.List;
-
/**
* InfoMediaDevice extends MediaDevice to represents wifi device.
*/
@@ -69,13 +71,12 @@
@Override
public Drawable getIconWithoutBackground() {
- return mContext.getDrawable(getDrawableResIdByFeature());
+ return mContext.getDrawable(getDrawableResIdByType());
}
@VisibleForTesting
- // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
- int getDrawableResId() {
+ int getDrawableResIdByType() {
int resId;
switch (mRouteInfo.getType()) {
case TYPE_GROUP:
@@ -84,6 +85,24 @@
case TYPE_REMOTE_TV:
resId = R.drawable.ic_media_display_device;
break;
+ case TYPE_REMOTE_TABLET:
+ resId = R.drawable.ic_media_tablet;
+ break;
+ case TYPE_REMOTE_TABLET_DOCKED:
+ resId = R.drawable.ic_dock_device;
+ break;
+ case TYPE_REMOTE_COMPUTER:
+ resId = R.drawable.ic_media_computer;
+ break;
+ case TYPE_REMOTE_GAME_CONSOLE:
+ resId = R.drawable.ic_media_game_console;
+ break;
+ case TYPE_REMOTE_CAR:
+ resId = R.drawable.ic_media_car;
+ break;
+ case TYPE_REMOTE_SMARTWATCH:
+ resId = R.drawable.ic_media_smartwatch;
+ break;
case TYPE_REMOTE_SPEAKER:
default:
resId = R.drawable.ic_media_speaker_device;
@@ -92,21 +111,6 @@
return resId;
}
- @VisibleForTesting
- int getDrawableResIdByFeature() {
- int resId;
- final List<String> features = mRouteInfo.getFeatures();
- if (features.contains(FEATURE_REMOTE_GROUP_PLAYBACK)) {
- resId = R.drawable.ic_media_group_device;
- } else if (features.contains(FEATURE_REMOTE_VIDEO_PLAYBACK)) {
- resId = R.drawable.ic_media_display_device;
- } else {
- resId = R.drawable.ic_media_speaker_device;
- }
-
- return resId;
- }
-
@Override
public String getId() {
return MediaDeviceUtils.getId(mRouteInfo);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 82c6f11..3fcb7f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -23,7 +23,13 @@
import static android.media.MediaRoute2Info.TYPE_HDMI;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
@@ -531,7 +537,6 @@
@SuppressWarnings("NewApi")
@VisibleForTesting
void addMediaDevice(MediaRoute2Info route) {
- //TODO(b/258141461): Attach flag and disable reason in MediaDevice
final int deviceType = route.getType();
MediaDevice mediaDevice = null;
switch (deviceType) {
@@ -539,7 +544,12 @@
case TYPE_REMOTE_TV:
case TYPE_REMOTE_SPEAKER:
case TYPE_GROUP:
- //TODO(b/148765806): use correct device type once api is ready.
+ case TYPE_REMOTE_TABLET:
+ case TYPE_REMOTE_TABLET_DOCKED:
+ case TYPE_REMOTE_COMPUTER:
+ case TYPE_REMOTE_GAME_CONSOLE:
+ case TYPE_REMOTE_CAR:
+ case TYPE_REMOTE_SMARTWATCH:
mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
mPackageName, mPreferenceItemMap.get(route.getId()));
break;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index c45b7f3..67a045e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -16,11 +16,14 @@
package com.android.settingslib.media;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK;
-import static android.media.MediaRoute2Info.FEATURE_REMOTE_VIDEO_PLAYBACK;
import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_COMPUTER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_GAME_CONSOLE;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SMARTWATCH;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TABLET_DOCKED;
import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
import static com.google.common.truth.Truth.assertThat;
@@ -41,8 +44,6 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
-import java.util.ArrayList;
-
@RunWith(RobolectricTestRunner.class)
public class InfoMediaDeviceTest {
@@ -100,40 +101,47 @@
public void getDrawableResId_returnCorrectResId() {
when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TV);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_display_device);
when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_speaker_device);
when(mRouteInfo.getType()).thenReturn(TYPE_GROUP);
- assertThat(mInfoMediaDevice.getDrawableResId()).isEqualTo(R.drawable.ic_media_group_device);
- }
-
- @Test
- public void getDrawableResIdByFeature_returnCorrectResId() {
- final ArrayList<String> features = new ArrayList<>();
- features.add(FEATURE_REMOTE_VIDEO_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
- R.drawable.ic_media_display_device);
-
- features.clear();
- features.add(FEATURE_REMOTE_AUDIO_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
- R.drawable.ic_media_speaker_device);
-
- features.clear();
- features.add(FEATURE_REMOTE_GROUP_PLAYBACK);
- when(mRouteInfo.getFeatures()).thenReturn(features);
-
- assertThat(mInfoMediaDevice.getDrawableResIdByFeature()).isEqualTo(
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
R.drawable.ic_media_group_device);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TABLET);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_tablet);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_TABLET_DOCKED);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_dock_device);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_COMPUTER);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_computer);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_GAME_CONSOLE);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_game_console);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_CAR);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_car);
+
+ when(mRouteInfo.getType()).thenReturn(TYPE_REMOTE_SMARTWATCH);
+
+ assertThat(mInfoMediaDevice.getDrawableResIdByType()).isEqualTo(
+ R.drawable.ic_media_smartwatch);
}
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9d3620e..ef4b814 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -116,6 +116,7 @@
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
+ Settings.Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS,
Settings.Global.SATELLITE_MODE_RADIOS,
Settings.Global.SATELLITE_MODE_ENABLED,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.kt
new file mode 100644
index 0000000..5224c51
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/grid/Grids.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.compose.grid
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.math.roundToInt
+
+/**
+ * Renders a grid with [columns] columns.
+ *
+ * Child composables will be arranged row by row.
+ *
+ * Each column is spaced from the columns to its left and right by [horizontalSpacing]. Each cell
+ * inside a column is spaced from the cells above and below it with [verticalSpacing].
+ */
+@Composable
+fun VerticalGrid(
+ columns: Int,
+ modifier: Modifier = Modifier,
+ verticalSpacing: Dp = 0.dp,
+ horizontalSpacing: Dp = 0.dp,
+ content: @Composable () -> Unit,
+) {
+ Grid(
+ primarySpaces = columns,
+ isVertical = true,
+ modifier = modifier,
+ verticalSpacing = verticalSpacing,
+ horizontalSpacing = horizontalSpacing,
+ content = content,
+ )
+}
+
+/**
+ * Renders a grid with [rows] rows.
+ *
+ * Child composables will be arranged column by column.
+ *
+ * Each column is spaced from the columns to its left and right by [horizontalSpacing]. Each cell
+ * inside a column is spaced from the cells above and below it with [verticalSpacing].
+ */
+@Composable
+fun HorizontalGrid(
+ rows: Int,
+ modifier: Modifier = Modifier,
+ verticalSpacing: Dp = 0.dp,
+ horizontalSpacing: Dp = 0.dp,
+ content: @Composable () -> Unit,
+) {
+ Grid(
+ primarySpaces = rows,
+ isVertical = false,
+ modifier = modifier,
+ verticalSpacing = verticalSpacing,
+ horizontalSpacing = horizontalSpacing,
+ content = content,
+ )
+}
+
+@Composable
+private fun Grid(
+ primarySpaces: Int,
+ isVertical: Boolean,
+ modifier: Modifier = Modifier,
+ verticalSpacing: Dp,
+ horizontalSpacing: Dp,
+ content: @Composable () -> Unit,
+) {
+ check(primarySpaces > 0) {
+ "Must provide a positive number of ${if (isVertical) "columns" else "rows"}"
+ }
+
+ val sizeCache = remember {
+ object {
+ var rowHeights = intArrayOf()
+ var columnWidths = intArrayOf()
+ }
+ }
+
+ Layout(
+ modifier = modifier,
+ content = content,
+ ) { measurables, constraints ->
+ val cells = measurables.size
+ val columns: Int
+ val rows: Int
+ if (isVertical) {
+ columns = primarySpaces
+ rows = ceil(cells.toFloat() / primarySpaces).toInt()
+ } else {
+ columns = ceil(cells.toFloat() / primarySpaces).toInt()
+ rows = primarySpaces
+ }
+
+ if (sizeCache.rowHeights.size != rows) {
+ sizeCache.rowHeights = IntArray(rows) { 0 }
+ }
+ if (sizeCache.columnWidths.size != columns) {
+ sizeCache.columnWidths = IntArray(columns) { 0 }
+ }
+
+ val totalHorizontalSpacingBetweenChildren =
+ ((columns - 1) * horizontalSpacing.toPx()).roundToInt()
+ val totalVerticalSpacingBetweenChildren = ((rows - 1) * verticalSpacing.toPx()).roundToInt()
+ val childConstraints =
+ Constraints().apply {
+ if (constraints.maxWidth != Constraints.Infinity) {
+ constrainWidth(
+ (constraints.maxWidth - totalHorizontalSpacingBetweenChildren) / columns
+ )
+ }
+ if (constraints.maxHeight != Constraints.Infinity) {
+ constrainWidth(
+ (constraints.maxHeight - totalVerticalSpacingBetweenChildren) / rows
+ )
+ }
+ }
+
+ val placeables = buildList {
+ for (cellIndex in measurables.indices) {
+ val column: Int
+ val row: Int
+ if (isVertical) {
+ column = cellIndex % columns
+ row = cellIndex / columns
+ } else {
+ column = cellIndex / rows
+ row = cellIndex % rows
+ }
+
+ val placeable = measurables[cellIndex].measure(childConstraints)
+ sizeCache.rowHeights[row] = max(sizeCache.rowHeights[row], placeable.height)
+ sizeCache.columnWidths[column] =
+ max(sizeCache.columnWidths[column], placeable.width)
+ add(placeable)
+ }
+ }
+
+ var totalWidth = totalHorizontalSpacingBetweenChildren
+ for (column in sizeCache.columnWidths.indices) {
+ totalWidth += sizeCache.columnWidths[column]
+ }
+
+ var totalHeight = totalVerticalSpacingBetweenChildren
+ for (row in sizeCache.rowHeights.indices) {
+ totalHeight += sizeCache.rowHeights[row]
+ }
+
+ layout(totalWidth, totalHeight) {
+ var y = 0
+ repeat(rows) { row ->
+ var x = 0
+ var maxChildHeight = 0
+ repeat(columns) { column ->
+ val cellIndex = row * columns + column
+ if (cellIndex < cells) {
+ val placeable = placeables[cellIndex]
+ placeable.placeRelative(x, y)
+ x += placeable.width + horizontalSpacing.roundToPx()
+ maxChildHeight = max(maxChildHeight, placeable.height)
+ }
+ }
+ y += maxChildHeight + verticalSpacing.roundToPx()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 853300cf..c6c3019 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -16,18 +16,30 @@
package com.android.systemui.bouncer.ui.composable
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -55,26 +67,69 @@
)
.asStateFlow()
- @Composable
- override fun Content(
- modifier: Modifier,
- ) {
- // TODO(b/280877228): implement the real UI.
+ @Composable override fun Content(modifier: Modifier) = BouncerScene(viewModel, modifier)
+}
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Bouncer", style = MaterialTheme.typography.headlineLarge)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(onClick = { viewModel.onAuthenticateButtonClicked() }) {
- Text("Authenticate")
- }
- }
+@Composable
+private fun BouncerScene(
+ viewModel: BouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val message: String by viewModel.message.collectAsState()
+ val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(60.dp),
+ modifier =
+ modifier.background(MaterialTheme.colorScheme.surface).fillMaxSize().padding(32.dp)
+ ) {
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ ) {
+ Text(
+ text = it,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
+ }
+
+ Box(Modifier.weight(1f)) {
+ when (val nonNullViewModel = authMethodViewModel) {
+ is PinBouncerViewModel ->
+ PinBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PasswordBouncerViewModel ->
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PatternBouncerViewModel ->
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+ .align(Alignment.BottomCenter),
+ )
+ else -> Unit
}
}
+
+ Button(
+ onClick = viewModel::onEmergencyServicesButtonClicked,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
new file mode 100644
index 0000000..4e85621
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.bouncer.ui.composable
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+
+/** UI for the input part of a password-requiring version of the bouncer. */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun PasswordBouncer(
+ viewModel: PasswordBouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val focusRequester = remember { FocusRequester() }
+ val password: String by viewModel.password.collectAsState()
+
+ LaunchedEffect(Unit) {
+ // When the UI comes up, request focus on the TextField to bring up the software keyboard.
+ focusRequester.requestFocus()
+ // Also, report that the UI is shown to let the view-model runs some logic.
+ viewModel.onShown()
+ }
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier,
+ ) {
+ val color = MaterialTheme.colorScheme.onSurfaceVariant
+ val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
+
+ TextField(
+ value = password,
+ onValueChange = viewModel::onPasswordInputChanged,
+ visualTransformation = PasswordVisualTransformation(),
+ singleLine = true,
+ textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+ keyboardOptions =
+ KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions =
+ KeyboardActions(
+ onDone = { viewModel.onAuthenticateKeyPressed() },
+ ),
+ modifier =
+ Modifier.focusRequester(focusRequester).drawBehind {
+ drawLine(
+ color = color,
+ start = Offset(x = 0f, y = size.height - lineWidthPx),
+ end = Offset(size.width, y = size.height - lineWidthPx),
+ strokeWidth = lineWidthPx,
+ )
+ },
+ )
+
+ Spacer(Modifier.height(100.dp))
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
new file mode 100644
index 0000000..383c748
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.bouncer.ui.composable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.launch
+
+/**
+ * UI for the input part of a pattern-requiring version of the bouncer.
+ *
+ * The user can press, hold, and drag their pointer to select dots along a grid of dots.
+ */
+@Composable
+internal fun PatternBouncer(
+ viewModel: PatternBouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // Report that the UI is shown to let the view-model run some logic.
+ LaunchedEffect(Unit) { viewModel.onShown() }
+
+ val colCount = viewModel.columnCount
+ val rowCount = viewModel.rowCount
+
+ val dotColor = MaterialTheme.colorScheme.secondary
+ val dotRadius = with(LocalDensity.current) { 8.dp.toPx() }
+ val lineColor = MaterialTheme.colorScheme.primary
+ val lineStrokeWidth = dotRadius * 2 + with(LocalDensity.current) { 4.dp.toPx() }
+
+ var containerSize: IntSize by remember { mutableStateOf(IntSize(0, 0)) }
+ val horizontalSpacing = containerSize.width / colCount
+ val verticalSpacing = containerSize.height / rowCount
+ val spacing = min(horizontalSpacing, verticalSpacing).toFloat()
+ val verticalOffset = containerSize.height - spacing * rowCount
+
+ // All dots that should be rendered on the grid.
+ val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState()
+ // The most recently selected dot, if the user is currently dragging.
+ val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState()
+ // The dots selected so far, if the user is currently dragging.
+ val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
+
+ // Map of animatables for the scale of each dot, keyed by dot.
+ val scales = remember(dots) { dots.associateWith { Animatable(1f) } }
+ // Map of animatables for the lines that connect between selected dots, keyed by the destination
+ // dot of the line.
+ val lines = remember(dots) { dots.associateWith { Animatable(1f) } }
+
+ val scope = rememberCoroutineScope()
+
+ // When the current dot is changed, we need to update our animations.
+ LaunchedEffect(currentDot) {
+ // Make sure that the current dot is scaled up while the other dots are scaled back down.
+ scales.entries.forEach { (dot, animatable) ->
+ val isSelected = dot == currentDot
+ launch {
+ animatable.animateTo(if (isSelected) 2f else 1f)
+ if (isSelected) {
+ animatable.animateTo(1f)
+ }
+ }
+ }
+
+ // Make sure that all dot-connecting lines are decaying, if they're not already animating.
+ selectedDots.forEach {
+ lines[it]?.let { line ->
+ if (!line.isRunning) {
+ scope.launch {
+ line.animateTo(
+ targetValue = 0f,
+ animationSpec = tween(durationMillis = 500),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ // This is the position of the input pointer.
+ var inputPosition: Offset? by remember { mutableStateOf(null) }
+
+ Canvas(
+ modifier
+ // Need to clip to bounds to make sure that the lines don't follow the input pointer
+ // when it leaves the bounds of the dot grid.
+ .clipToBounds()
+ .onSizeChanged { containerSize = it }
+ .pointerInput(Unit) {
+ detectDragGestures(
+ onDragStart = { start ->
+ inputPosition = start
+ viewModel.onDragStart()
+ },
+ onDragEnd = {
+ inputPosition = null
+ lines.values.forEach { animatable ->
+ scope.launch { animatable.animateTo(1f) }
+ }
+ viewModel.onDragEnd()
+ },
+ ) { change, _ ->
+ inputPosition = change.position
+ viewModel.onDrag(
+ xPx = change.position.x,
+ yPx = change.position.y,
+ containerSizePx = containerSize.width,
+ verticalOffsetPx = verticalOffset,
+ )
+ }
+ }
+ ) {
+ // Draw lines between dots.
+ selectedDots.forEachIndexed { index, dot ->
+ if (index > 0) {
+ val previousDot = selectedDots[index - 1]
+ drawLine(
+ from = previousDot,
+ to = dot,
+ alpha = { distance -> lineAlpha(spacing, distance) },
+ spacing = spacing,
+ verticalOffset = verticalOffset,
+ lineColor = lineColor,
+ lineStrokeWidth = lineStrokeWidth,
+ )
+ }
+ }
+
+ // Draw the line between the most recently-selected dot and the input pointer position.
+ inputPosition?.let { lineEnd ->
+ currentDot?.let { dot ->
+ drawLine(
+ from = dot,
+ to = lineEnd,
+ alpha = { distance -> lineAlpha(spacing, distance) },
+ spacing = spacing,
+ verticalOffset = verticalOffset,
+ lineColor = lineColor,
+ lineStrokeWidth = lineStrokeWidth,
+ )
+ }
+ }
+
+ // Draw each dot on the grid.
+ dots.forEach { dot ->
+ drawDot(
+ dot = dot,
+ scaleFactor = { scales[dot]?.value ?: 1f },
+ spacing = spacing,
+ verticalOffset = verticalOffset,
+ dotColor = dotColor,
+ dotRadius = dotRadius,
+ )
+ }
+ }
+}
+
+/** Draws the given [dot]. */
+private fun DrawScope.drawDot(
+ dot: PatternDotViewModel,
+ scaleFactor: () -> Float,
+ spacing: Float,
+ verticalOffset: Float,
+ dotColor: Color,
+ dotRadius: Float,
+) {
+ drawCircle(
+ color = dotColor,
+ radius = dotRadius * scaleFactor.invoke(),
+ center = pixelOffset(dot, spacing, verticalOffset),
+ )
+}
+
+/** Draws a line from the [from] origin dot to the [to] destination dot. */
+private fun DrawScope.drawLine(
+ from: PatternDotViewModel,
+ to: PatternDotViewModel,
+ alpha: (distance: Float) -> Float,
+ spacing: Float,
+ verticalOffset: Float,
+ lineColor: Color,
+ lineStrokeWidth: Float,
+) {
+ drawLine(
+ from = from,
+ to = pixelOffset(to, spacing, verticalOffset),
+ alpha = alpha,
+ spacing = spacing,
+ verticalOffset = verticalOffset,
+ lineColor = lineColor,
+ lineStrokeWidth = lineStrokeWidth,
+ )
+}
+
+/** Draws a line from the [from] origin dot to the [to] destination. */
+private fun DrawScope.drawLine(
+ from: PatternDotViewModel,
+ to: Offset,
+ alpha: (distance: Float) -> Float,
+ spacing: Float,
+ verticalOffset: Float,
+ lineColor: Color,
+ lineStrokeWidth: Float,
+) {
+ val fromAsOffset = pixelOffset(from, spacing, verticalOffset)
+ val distance = sqrt((to.y - fromAsOffset.y).pow(2) + (to.x - fromAsOffset.x).pow(2))
+
+ drawLine(
+ color = lineColor,
+ start = fromAsOffset,
+ end = to,
+ strokeWidth = lineStrokeWidth,
+ cap = StrokeCap.Round,
+ alpha = alpha.invoke(distance),
+ )
+}
+
+/** Returns an [Offset] representation of the given [dot] in pixel coordinates. */
+private fun pixelOffset(
+ dot: PatternDotViewModel,
+ spacing: Float,
+ verticalOffset: Float,
+): Offset {
+ return Offset(
+ x = dot.x * spacing + spacing / 2,
+ y = dot.y * spacing + spacing / 2 + verticalOffset,
+ )
+}
+
+/**
+ * Returns the alpha for a line between dots where dots are [spacing] apart from each other on the
+ * dot grid and the line ends [distance] away from the origin dot.
+ *
+ * The reason [distance] can be different from [spacing] is that all lines originate in dots but one
+ * line might end where the user input pointer is, which isn't always a dot position.
+ */
+private fun lineAlpha(spacing: Float, distance: Float): Float {
+ // Custom curve for the alpha of a line as a function of its distance from its source dot. The
+ // farther the user input pointer goes from the line, the more opaque the line gets.
+ return ((distance / spacing - 0.3f) * 4f).coerceIn(0f, 1f)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
new file mode 100644
index 0000000..9c210c2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalAnimationApi::class)
+
+package com.android.systemui.bouncer.ui.composable
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.android.compose.grid.VerticalGrid
+import com.android.systemui.R
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import kotlin.math.max
+
+@Composable
+internal fun PinBouncer(
+ viewModel: PinBouncerViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // Report that the UI is shown to let the view-model run some logic.
+ LaunchedEffect(Unit) { viewModel.onShown() }
+
+ // The length of the PIN input received so far, so we know how many dots to render.
+ val pinLength: Pair<Int, Int> by viewModel.pinLengths.collectAsState()
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier,
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.heightIn(min = 16.dp).animateContentSize(),
+ ) {
+ // TODO(b/281871687): add support for dot shapes.
+ val (previousPinLength, currentPinLength) = pinLength
+ val dotCount = max(previousPinLength, currentPinLength) + 1
+ repeat(dotCount) { index ->
+ AnimatedVisibility(
+ visible = index < currentPinLength,
+ enter = fadeIn() + scaleIn() + slideInHorizontally(),
+ exit = fadeOut() + scaleOut() + slideOutHorizontally(),
+ ) {
+ Box(
+ modifier =
+ Modifier.size(16.dp)
+ .background(
+ MaterialTheme.colorScheme.onSurfaceVariant,
+ CircleShape,
+ )
+ )
+ }
+ }
+ }
+
+ Spacer(Modifier.height(100.dp))
+
+ VerticalGrid(
+ columns = 3,
+ verticalSpacing = 12.dp,
+ horizontalSpacing = 20.dp,
+ ) {
+ repeat(9) { index ->
+ val digit = index + 1
+ PinButton(
+ onClicked = { viewModel.onPinButtonClicked(digit) },
+ ) { contentColor ->
+ PinDigit(digit, contentColor)
+ }
+ }
+
+ PinButton(
+ onClicked = { viewModel.onBackspaceButtonClicked() },
+ onLongPressed = { viewModel.onBackspaceButtonLongPressed() },
+ isHighlighted = true,
+ ) { contentColor ->
+ PinIcon(
+ Icon.Resource(
+ res = R.drawable.ic_backspace_24dp,
+ contentDescription =
+ ContentDescription.Resource(R.string.keyboardview_keycode_delete),
+ ),
+ contentColor,
+ )
+ }
+
+ PinButton(
+ onClicked = { viewModel.onPinButtonClicked(0) },
+ ) { contentColor ->
+ PinDigit(0, contentColor)
+ }
+
+ PinButton(
+ onClicked = { viewModel.onAuthenticateButtonClicked() },
+ isHighlighted = true,
+ ) { contentColor ->
+ PinIcon(
+ Icon.Resource(
+ res = R.drawable.ic_keyboard_tab_36dp,
+ contentDescription =
+ ContentDescription.Resource(R.string.keyboardview_keycode_enter),
+ ),
+ contentColor,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun PinDigit(
+ digit: Int,
+ contentColor: Color,
+) {
+ // TODO(b/281878426): once "color: () -> Color" (added to BasicText in aosp/2568972) makes it
+ // into Text, use that here, to animate more efficiently.
+ Text(
+ text = digit.toString(),
+ style = MaterialTheme.typography.headlineLarge,
+ color = contentColor,
+ )
+}
+
+@Composable
+private fun PinIcon(
+ icon: Icon,
+ contentColor: Color,
+) {
+ Icon(
+ icon = icon,
+ tint = contentColor,
+ )
+}
+
+@Composable
+private fun PinButton(
+ onClicked: () -> Unit,
+ modifier: Modifier = Modifier,
+ onLongPressed: (() -> Unit)? = null,
+ isHighlighted: Boolean = false,
+ content: @Composable (contentColor: Color) -> Unit,
+) {
+ var isPressed: Boolean by remember { mutableStateOf(false) }
+ val cornerRadius: Dp by
+ animateDpAsState(
+ if (isPressed) 24.dp else PinButtonSize / 2,
+ label = "PinButton round corners",
+ )
+ val containerColor: Color by
+ animateColorAsState(
+ when {
+ isPressed -> MaterialTheme.colorScheme.primaryContainer
+ isHighlighted -> MaterialTheme.colorScheme.secondaryContainer
+ else -> MaterialTheme.colorScheme.surface
+ },
+ label = "Pin button container color",
+ )
+ val contentColor: Color by
+ animateColorAsState(
+ when {
+ isPressed -> MaterialTheme.colorScheme.onPrimaryContainer
+ isHighlighted -> MaterialTheme.colorScheme.onSecondaryContainer
+ else -> MaterialTheme.colorScheme.onSurface
+ },
+ label = "Pin button container color",
+ )
+
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier =
+ modifier
+ .size(PinButtonSize)
+ .drawBehind {
+ drawRoundRect(
+ color = containerColor,
+ cornerRadius = CornerRadius(cornerRadius.toPx()),
+ )
+ }
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onPress = {
+ isPressed = true
+ tryAwaitRelease()
+ isPressed = false
+ },
+ onTap = { onClicked() },
+ onLongPress = onLongPressed?.let { { onLongPressed() } },
+ )
+ },
+ ) {
+ content(contentColor)
+ }
+}
+
+private val PinButtonSize = 84.dp
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/config.xml b/packages/SystemUI/res-keyguard/values/config.xml
index a25ab51..d503551 100644
--- a/packages/SystemUI/res-keyguard/values/config.xml
+++ b/packages/SystemUI/res-keyguard/values/config.xml
@@ -28,6 +28,8 @@
<!-- Will display the bouncer on one side of the display, and the current user icon and
user switcher on the other side -->
<bool name="config_enableBouncerUserSwitcher">false</bool>
+ <!-- Will enable custom clocks on the lockscreen -->
+ <bool name="config_enableLockScreenCustomClocks">true</bool>
<!-- Time to be considered a consecutive fingerprint failure in ms -->
<integer name="fp_consecutive_failure_time_ms">3500</integer>
</resources>
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/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index e182a6a..9c1dc64 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -31,7 +31,7 @@
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
- android:paddingBottom="24dp"
+ android:paddingBottom="16dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/header_icon"
@@ -113,6 +113,8 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_result"
android:scrollbars="vertical"
+ android:paddingTop="8dp"
+ android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index db8191b..a8febe7 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -112,9 +112,13 @@
android:focusable="true">
<TextView
+ android:id="@+id/magnifier_horizontal_lock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
+ android:singleLine="true"
+ android:scrollHorizontally="true"
+ android:ellipsize="marquee"
android:text="@string/accessibility_allow_diagonal_scrolling"
android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
android:layout_gravity="center_vertical" />
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 2024dae..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>
@@ -1318,6 +1319,7 @@
<dimen name="media_output_dialog_active_background_radius">28dp</dimen>
<dimen name="media_output_dialog_default_margin_end">16dp</dimen>
<dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
+ <dimen name="media_output_dialog_list_padding_top">8dp</dimen>
<!-- Distance that the full shade transition takes in order to complete by tapping on a button
like "expand". -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5795e4f..7dc8afe 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2583,7 +2583,7 @@
<!-- Title for media controls [CHAR_LIMIT=50] -->
<string name="controls_media_title">Media</string>
<!-- Explanation for closing controls associated with a specific media session [CHAR_LIMIT=50] -->
- <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="YouTube Music">%1$s</xliff:g>?</string>
+ <string name="controls_media_close_session">Hide this media control for <xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g>?</string>
<!-- Explanation that controls associated with a specific media session are active [CHAR_LIMIT=50] -->
<string name="controls_media_active_session">The current media session cannot be hidden.</string>
<!-- Label for a button that will hide media controls [CHAR_LIMIT=30] -->
@@ -2596,6 +2596,8 @@
<string name="controls_media_playing_item_description"><xliff:g id="song_name" example="Daily mix">%1$s</xliff:g> by <xliff:g id="artist_name" example="Various artists">%2$s</xliff:g> is playing from <xliff:g id="app_label" example="Spotify">%3$s</xliff:g></string>
<!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
<string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
+ <!-- Placeholder title to inform user that an app has posted media controls [CHAR_LIMIT=NONE] -->
+ <string name="controls_media_empty_title"><xliff:g id="app_name" example="Foo Music App">%1$s</xliff:g> is running</string>
<!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
<string name="controls_media_button_play">Play</string>
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 510fcbf..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;
@@ -179,10 +181,10 @@
handleAttemptLockout(deadline);
}
}
+ mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
if (timeoutMs == 0) {
mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
}
- mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
startErrorAnimation();
}
}
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 c1896fc..0332c9f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,7 +18,9 @@
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.text.Editable;
import android.text.TextUtils;
+import android.text.TextWatcher;
import android.view.View;
import androidx.annotation.VisibleForTesting;
@@ -45,6 +47,31 @@
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final AnnounceRunnable mAnnounceRunnable;
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable editable) {
+ CharSequence msg = editable;
+ if (!TextUtils.isEmpty(msg)) {
+ mView.removeCallbacks(mAnnounceRunnable);
+ mAnnounceRunnable.setTextToAnnounce(msg);
+ mView.postDelayed(() -> {
+ if (msg == mView.getText()) {
+ mAnnounceRunnable.run();
+ }
+ }, ANNOUNCEMENT_DELAY);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ /* no-op */
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+ /* no-op */
+ }
+ };
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
@@ -89,12 +116,14 @@
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
mView.onThemeChanged();
+ mView.addTextChangedListener(mTextWatcher);
}
@Override
protected void onViewDetached() {
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mView.removeTextChangedListener(mTextWatcher);
}
/**
@@ -104,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);
}
@@ -112,13 +149,10 @@
* Sets a message to the underlying text view.
*/
public void setMessage(CharSequence s, boolean animate) {
- mView.setMessage(s, animate);
- CharSequence msg = mView.getText();
- if (!TextUtils.isEmpty(msg)) {
- mView.removeCallbacks(mAnnounceRunnable);
- mAnnounceRunnable.setTextToAnnounce(msg);
- mView.postDelayed(mAnnounceRunnable, ANNOUNCEMENT_DELAY);
+ if (mView.isDisabled()) {
+ return;
}
+ mView.setMessage(s, animate);
}
public void setMessage(int resId) {
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 b4ddc9a..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;
@@ -82,9 +84,6 @@
protected void setPasswordEntryInputEnabled(boolean enabled) {
mPasswordEntry.setEnabled(enabled);
mOkButton.setEnabled(enabled);
- if (enabled && !mPasswordEntry.hasFocus()) {
- mPasswordEntry.requestFocus();
- }
}
@Override
@@ -150,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 f23bb0a..f191281 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -16,8 +16,6 @@
package com.android.keyguard;
-import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.DEFAULT_PIN_LENGTH;
-
import android.view.View;
import com.android.internal.util.LatencyTracker;
@@ -42,10 +40,9 @@
private NumPadButton mBackspaceKey;
private View mOkButton = mView.findViewById(R.id.key_enter);
- private int mUserId;
private long mPinLength;
- private int mPasswordFailedAttempts;
+ private boolean mDisabledAutoConfirmation;
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -59,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;
@@ -84,9 +81,8 @@
protected void onUserInput() {
super.onUserInput();
- if (isAutoConfirmation()) {
- updateOKButtonVisibility();
- updateBackSpaceVisibility();
+ if (isAutoPinConfirmEnabledInSettings()) {
+ updateAutoConfirmationState();
if (mPasswordEntry.getText().length() == mPinLength
&& mOkButton.getVisibility() == View.INVISIBLE) {
verifyPasswordAndUnlock();
@@ -103,13 +99,9 @@
@Override
public void startAppearAnimation() {
if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
- mUserId = KeyguardUpdateMonitor.getCurrentUser();
- mPinLength = mLockPatternUtils.getPinLength(mUserId);
- mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
- updateOKButtonVisibility();
- updateBackSpaceVisibility();
+ mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
mPasswordEntry.setUsePinShapes(true);
- mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
+ updateAutoConfirmationState();
}
super.startAppearAnimation();
}
@@ -120,13 +112,25 @@
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
+ @Override
+ protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
+ super.handleAttemptLockout(elapsedRealtimeDeadline);
+ updateAutoConfirmationState();
+ }
+
+ private void updateAutoConfirmationState() {
+ mDisabledAutoConfirmation = mLockPatternUtils.getCurrentFailedPasswordAttempts(
+ KeyguardUpdateMonitor.getCurrentUser()) >= MIN_FAILED_PIN_ATTEMPTS;
+ updateOKButtonVisibility();
+ updateBackSpaceVisibility();
+ updatePinHinting();
+ }
/**
* Updates the visibility of the OK button for auto confirm feature
*/
private void updateOKButtonVisibility() {
- mPasswordFailedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(mUserId);
- if (isAutoConfirmation() && mPasswordFailedAttempts < MIN_FAILED_PIN_ATTEMPTS) {
+ if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) {
mOkButton.setVisibility(View.INVISIBLE);
} else {
mOkButton.setVisibility(View.VISIBLE);
@@ -134,33 +138,41 @@
}
/**
- * Updates the visibility and the enabled state of the backspace.
+ * Updates the visibility and the enabled state of the backspace.
* Visibility changes are only for auto confirmation configuration.
*/
private void updateBackSpaceVisibility() {
- if (!isAutoConfirmation()) {
- return;
+ boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings();
+ mBackspaceKey.setTransparentMode(/* isTransparentMode= */
+ isAutoConfirmation && !mDisabledAutoConfirmation);
+ if (isAutoConfirmation) {
+ if (mPasswordEntry.getText().length() > 0
+ || mDisabledAutoConfirmation) {
+ mBackspaceKey.setVisibility(View.VISIBLE);
+ } else {
+ mBackspaceKey.setVisibility(View.INVISIBLE);
+ }
}
-
- if (mPasswordEntry.getText().length() > 0) {
- mBackspaceKey.setVisibility(View.VISIBLE);
- } else {
- mBackspaceKey.setVisibility(View.INVISIBLE);
- }
+ }
+ /** Updates whether to use pin hinting or not. */
+ void updatePinHinting() {
+ mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting()
+ && !mDisabledAutoConfirmation);
}
/**
- * Responsible for identifying if PIN hinting is to be enabled or not
+ * Responsible for identifying if PIN hinting is to be enabled or not
*/
private boolean isPinHinting() {
- return mLockPatternUtils.getPinLength(mUserId) == DEFAULT_PIN_LENGTH;
+ return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser())
+ == DEFAULT_PIN_LENGTH;
}
/**
- * Responsible for identifying if auto confirm is enabled or not in Settings
+ * Responsible for identifying if auto confirm is enabled or not in Settings
*/
- private boolean isAutoConfirmation() {
+ private boolean isAutoPinConfirmEnabledInSettings() {
//Checks if user has enabled the auto confirm in Settings
- return mLockPatternUtils.isAutoPinConfirmEnabled(mUserId);
+ return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser());
}
}
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 95e97ff..e8046dc0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -29,6 +29,7 @@
import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -1235,7 +1236,6 @@
*/
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
- mLogger.logFaceAcquired(acquireInfo);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1288,7 +1288,6 @@
return;
}
Assert.isMainThread();
- mLogger.logFaceAuthHelpMsg(msgId, helpString);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1462,6 +1461,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;
@@ -1970,6 +1977,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();
}
@@ -2444,7 +2458,8 @@
}
// Take a guess at initial SIM state, battery status and PLMN until we get an update
- mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0, true);
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, /* level= */ 100, /* plugged= */
+ 0, CHARGING_POLICY_DEFAULT, /* maxChargingWattage= */0, /* present= */true);
// Watch for interesting updates
final IntentFilter filter = new IntentFilter();
@@ -2623,6 +2638,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() {
@@ -3860,8 +3883,8 @@
return true;
}
- // change in battery overheat
- return current.health != old.health;
+ // change in charging status
+ return current.chargingStatus != old.chargingStatus;
}
/**
@@ -4395,7 +4418,8 @@
mFingerprintListenBuffer.toList()
).printTableData(pw);
}
-
+ pw.println("ActiveUnlockRunning="
+ + mTrustManager.isActiveUnlockRunning(KeyguardUpdateMonitor.getCurrentUser()));
new DumpsysTableLogger(
"KeyguardActiveUnlockTriggers",
KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 6ae80a6..ebd234f 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -116,7 +116,12 @@
* @param isTransparentMode
*/
public void setTransparentMode(boolean isTransparentMode) {
+ if (mIsTransparentMode == isTransparentMode) {
+ return;
+ }
+
mIsTransparentMode = isTransparentMode;
+
if (isTransparentMode) {
setBackgroundColor(getResources().getColor(android.R.color.transparent));
} else {
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 5400011..8e8ee48 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -101,6 +101,7 @@
private Interpolator mFastOutSlowInInterpolator;
private boolean mShowPassword = true;
private UserActivityListener mUserActivityListener;
+ private boolean mIsPinHinting;
private PinShapeInput mPinShapeInput;
private boolean mUsePinShapes = false;
@@ -419,10 +420,15 @@
/**
* Determines whether AutoConfirmation feature is on.
*
- * @param usePinShapes
* @param isPinHinting
*/
public void setIsPinHinting(boolean isPinHinting) {
+ // Do not reinflate the view if we are using the same one.
+ if (mPinShapeInput != null && mIsPinHinting == isPinHinting) {
+ return;
+ }
+ mIsPinHinting = isPinHinting;
+
if (mPinShapeInput != null) {
removeView(mPinShapeInput.getView());
mPinShapeInput = null;
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/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index c2d22c3..17cc236 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -95,10 +95,6 @@
logBuffer.log(TAG, ERROR, {}, { logMsg }, exception = ex)
}
- fun logFaceAcquired(acquireInfo: Int) {
- logBuffer.log(TAG, DEBUG, { int1 = acquireInfo }, { "Face acquired acquireInfo=$int1" })
- }
-
fun logFaceAuthDisabledForUser(userId: Int) {
logBuffer.log(
TAG,
@@ -128,18 +124,6 @@
)
}
- fun logFaceAuthHelpMsg(msgId: Int, helpMsg: String?) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- int1 = msgId
- str1 = helpMsg
- },
- { "Face help received, msgId: $int1 msg: $str1" }
- )
- }
-
fun logFaceAuthRequested(reason: String?) {
logBuffer.log(TAG, DEBUG, { str1 = reason }, { "requestFaceAuth() reason=$str1" })
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
index f05152a..cb764a8 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -17,6 +17,7 @@
package com.android.keyguard.logging
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.ActiveUnlockModel
import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.log.LogBuffer
@@ -76,6 +77,18 @@
)
}
+ fun activeUnlockModelEmitted(value: ActiveUnlockModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = value.userId
+ bool1 = value.isRunning
+ },
+ { "activeUnlockModel emitted: userId: $int1 isRunning: $bool1" }
+ )
+ }
+
fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
logBuffer.log(
TAG,
@@ -85,6 +98,15 @@
)
}
+ fun isCurrentUserActiveUnlockRunning(isCurrentUserActiveUnlockRunning: Boolean) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCurrentUserActiveUnlockRunning },
+ { "isCurrentUserActiveUnlockRunning emitted: $bool1" }
+ )
+ }
+
fun isCurrentUserTrustManaged(isTrustManaged: Boolean) {
logBuffer.log(TAG, DEBUG, { bool1 = isTrustManaged }, { "isTrustManaged emitted: $bool1" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
deleted file mode 100644
index 227f0ace..0000000
--- a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.android.systemui
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.pm.PackageManager
-import android.util.Log
-import com.android.internal.R
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.FlagListenable
-import com.android.systemui.flags.Flags
-import com.android.systemui.settings.UserTracker
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
-import kotlinx.coroutines.withContext
-import javax.inject.Inject
-
-@SysUISingleton
-class ChooserSelector @Inject constructor(
- private val context: Context,
- private val userTracker: UserTracker,
- private val featureFlags: FeatureFlags,
- @Application private val coroutineScope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher,
-) : CoreStartable {
-
- private val chooserComponent = ComponentName.unflattenFromString(
- context.resources.getString(R.string.config_chooserActivity))
-
- override fun start() {
- coroutineScope.launch {
- val listener = FlagListenable.Listener { event ->
- if (event.flagName == Flags.CHOOSER_UNBUNDLED.name) {
- launch { updateUnbundledChooserEnabled() }
- event.requestNoRestart()
- }
- }
- featureFlags.addListener(Flags.CHOOSER_UNBUNDLED, listener)
- updateUnbundledChooserEnabled()
-
- awaitCancellationAndThen { featureFlags.removeListener(listener) }
- }
- }
-
- private suspend fun updateUnbundledChooserEnabled() {
- setUnbundledChooserEnabled(withContext(bgDispatcher) {
- featureFlags.isEnabled(Flags.CHOOSER_UNBUNDLED)
- })
- }
-
- private fun setUnbundledChooserEnabled(enabled: Boolean) {
- val newState = if (enabled) {
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- } else {
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- }
- userTracker.userProfiles.forEach {
- try {
- context.createContextAsUser(it.userHandle, /* flags = */ 0).packageManager
- .setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
- } catch (e: IllegalArgumentException) {
- Log.w(
- "ChooserSelector",
- "Unable to set IntentResolver enabled=$enabled for user ${it.id}",
- e,
- )
- }
- }
- }
-
- suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
- suspend inline fun awaitCancellationAndThen(block: () -> Unit): Nothing = try {
- awaitCancellation()
- } finally {
- block()
- }
-}
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/accessibility/AccessibilityLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt
new file mode 100644
index 0000000..7b91596
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.accessibility
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import javax.inject.Inject
+
+/**
+ * Handles logging UiEvent stats for simple UI interactions.
+ *
+ * See go/uievent
+ */
+class AccessibilityLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
+ /** Logs the given event */
+ fun log(event: UiEventLogger.UiEventEnum) {
+ uiEventLogger.log(event)
+ }
+
+ /** Events regarding interaction with the magnifier settings panel */
+ enum class MagnificationSettingsEvent constructor(private val id: Int) :
+ UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Magnification settings panel opened.")
+ MAGNIFICATION_SETTINGS_PANEL_OPENED(1381),
+
+ @UiEvent(doc = "Magnification settings panel closed")
+ MAGNIFICATION_SETTINGS_PANEL_CLOSED(1382),
+
+ @UiEvent(doc = "Magnification settings panel edit size button clicked")
+ MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED(1383),
+
+ @UiEvent(doc = "Magnification settings panel edit size save button clicked")
+ MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED(1384),
+
+ @UiEvent(doc = "Magnification settings panel window size selected")
+ MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED(1386);
+
+ override fun getId(): Int = this.id
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index c820e4f..e2b85fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
import android.annotation.MainThread;
@@ -66,6 +67,7 @@
private final CommandQueue mCommandQueue;
private final OverviewProxyService mOverviewProxyService;
private final DisplayTracker mDisplayTracker;
+ private final AccessibilityLogger mA11yLogger;
private WindowMagnificationConnectionImpl mWindowMagnificationConnectionImpl;
private SysUiState mSysUiState;
@@ -151,7 +153,7 @@
CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
SysUiState sysUiState, OverviewProxyService overviewProxyService,
SecureSettings secureSettings, DisplayTracker displayTracker,
- DisplayManager displayManager) {
+ DisplayManager displayManager, AccessibilityLogger a11yLogger) {
mContext = context;
mHandler = mainHandler;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
@@ -160,6 +162,7 @@
mSysUiState = sysUiState;
mOverviewProxyService = overviewProxyService;
mDisplayTracker = displayTracker;
+ mA11yLogger = a11yLogger;
mMagnificationControllerSupplier = new ControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
@@ -343,6 +346,7 @@
@Override
public void onSetMagnifierSize(int displayId, int index) {
mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index));
+ mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED);
}
@Override
@@ -353,6 +357,9 @@
@Override
public void onEditMagnifierSizeMode(int displayId, boolean enable) {
mHandler.post(() -> onEditMagnifierSizeModeInternal(displayId, enable));
+ mA11yLogger.log(enable
+ ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED
+ : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED);
}
@Override
@@ -370,6 +377,9 @@
@Override
public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) {
mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown));
+ mA11yLogger.log(shown
+ ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED
+ : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 44b49b8..3b1d695 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -53,6 +53,7 @@
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
+import android.widget.TextView;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
@@ -87,6 +88,7 @@
private SeekBarWithIconButtonsView mZoomSeekbar;
private LinearLayout mAllowDiagonalScrollingView;
+ private TextView mAllowDiagonalScrollingTitle;
private Switch mAllowDiagonalScrollingSwitch;
private LinearLayout mPanelView;
private LinearLayout mSettingView;
@@ -467,6 +469,8 @@
mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button);
+ mAllowDiagonalScrollingTitle =
+ mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
float scale = mSecureSettings.getFloatForUser(
@@ -490,6 +494,7 @@
mDoneButton.setOnClickListener(mButtonClickListener);
mFullScreenButton.setOnClickListener(mButtonClickListener);
mEditButton.setOnClickListener(mButtonClickListener);
+ mAllowDiagonalScrollingTitle.setSelected(true);
mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
// Adds a pending post check to avoiding redundant calculation because this callback
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 2aac056..263df33 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -78,7 +78,7 @@
private boolean mShowPercentAvailable;
private String mEstimateText = null;
private boolean mCharging;
- private boolean mIsOverheated;
+ private boolean mIsBatteryDefender;
private boolean mDisplayShieldEnabled;
// Error state where we know nothing about the current battery state
private boolean mBatteryStateUnknown;
@@ -213,9 +213,9 @@
mDrawable.setPowerSaveEnabled(isPowerSave);
}
- void onIsOverheatedChanged(boolean isOverheated) {
- boolean valueChanged = mIsOverheated != isOverheated;
- mIsOverheated = isOverheated;
+ void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
+ boolean valueChanged = mIsBatteryDefender != isBatteryDefender;
+ mIsBatteryDefender = isBatteryDefender;
if (valueChanged) {
updateContentDescription();
// The battery drawable is a different size depending on whether it's currently
@@ -308,12 +308,12 @@
contentDescription = context.getString(R.string.accessibility_battery_unknown);
} else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) {
contentDescription = context.getString(
- mIsOverheated
+ mIsBatteryDefender
? R.string.accessibility_battery_level_charging_paused_with_estimate
: R.string.accessibility_battery_level_with_estimate,
mLevel,
mEstimateText);
- } else if (mIsOverheated) {
+ } else if (mIsBatteryDefender) {
contentDescription =
context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
} else if (mCharging) {
@@ -399,9 +399,7 @@
float mainBatteryWidth =
res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor;
- // If the battery is marked as overheated, we should display a shield indicating that the
- // battery is being "defended".
- boolean displayShield = mDisplayShieldEnabled && mIsOverheated;
+ boolean displayShield = mDisplayShieldEnabled && mIsBatteryDefender;
float fullBatteryIconHeight =
BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield);
float fullBatteryIconWidth =
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index f4ec33a..f6a10bd 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -91,8 +91,8 @@
}
@Override
- public void onIsOverheatedChanged(boolean isOverheated) {
- mView.onIsOverheatedChanged(isOverheated);
+ public void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
+ mView.onIsBatteryDefenderChanged(isBatteryDefender);
}
};
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/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
new file mode 100644
index 0000000..ebefb78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+sealed interface AuthMethodBouncerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 8a183ae..eaa8ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.content.Context
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.dagger.qualifiers.Application
@@ -30,12 +31,44 @@
class BouncerViewModel
@AssistedInject
constructor(
+ @Application private val applicationContext: Context,
@Application private val applicationScope: CoroutineScope,
interactorFactory: BouncerInteractor.Factory,
containerName: String,
) {
private val interactor: BouncerInteractor = interactorFactory.create(containerName)
+ private val pin: PinBouncerViewModel by lazy {
+ PinBouncerViewModel(
+ applicationScope = applicationScope,
+ interactor = interactor,
+ )
+ }
+
+ private val password: PasswordBouncerViewModel by lazy {
+ PasswordBouncerViewModel(
+ interactor = interactor,
+ )
+ }
+
+ private val pattern: PatternBouncerViewModel by lazy {
+ PatternBouncerViewModel(
+ applicationContext = applicationContext,
+ applicationScope = applicationScope,
+ interactor = interactor,
+ )
+ }
+
+ /** View-model for the current UI, based on the current authentication method. */
+ val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
+ interactor.authenticationMethod
+ .map { authMethod -> toViewModel(authMethod) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = toViewModel(interactor.authenticationMethod.value),
+ )
+
/** The user-facing message to show in the bouncer. */
val message: StateFlow<String> =
interactor.message
@@ -46,30 +79,19 @@
initialValue = interactor.message.value ?: "",
)
- /** Notifies that the authenticate button was clicked. */
- fun onAuthenticateButtonClicked() {
- // TODO(b/280877228): remove this and send the real input.
- interactor.authenticate(
- when (interactor.authenticationMethod.value) {
- is AuthenticationMethodModel.PIN -> listOf(1, 2, 3, 4)
- is AuthenticationMethodModel.Password -> "password".toList()
- is AuthenticationMethodModel.Pattern ->
- listOf(
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
- AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
- AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
- )
- else -> emptyList()
- }
- )
- }
-
/** Notifies that the emergency services button was clicked. */
fun onEmergencyServicesButtonClicked() {
- // TODO(b/280877228): implement this.
+ // TODO(b/280877228): implement this
+ }
+
+ private fun toViewModel(
+ authMethod: AuthenticationMethodModel,
+ ): AuthMethodBouncerViewModel? {
+ return when (authMethod) {
+ is AuthenticationMethodModel.PIN -> pin
+ is AuthenticationMethodModel.Password -> password
+ is AuthenticationMethodModel.Pattern -> pattern
+ else -> null
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
new file mode 100644
index 0000000..730d4e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.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.bouncer.ui.viewmodel
+
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Holds UI state and handles user input for the password bouncer UI. */
+class PasswordBouncerViewModel(
+ private val interactor: BouncerInteractor,
+) : AuthMethodBouncerViewModel {
+
+ private val _password = MutableStateFlow("")
+ /** The password entered so far. */
+ val password: StateFlow<String> = _password.asStateFlow()
+
+ /** Notifies that the UI has been shown to the user. */
+ fun onShown() {
+ interactor.resetMessage()
+ }
+
+ /** Notifies that the user has changed the password input. */
+ fun onPasswordInputChanged(password: String) {
+ if (this.password.value.isEmpty() && password.isNotEmpty()) {
+ interactor.clearMessage()
+ }
+
+ _password.value = password
+ }
+
+ /** Notifies that the user has pressed the key for attempting to authenticate the password. */
+ fun onAuthenticateKeyPressed() {
+ interactor.authenticate(password.value.toCharArray().toList())
+ _password.value = ""
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
new file mode 100644
index 0000000..eb1b457
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -0,0 +1,189 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import android.content.Context
+import android.util.TypedValue
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.sqrt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Holds UI state and handles user input for the pattern bouncer UI. */
+class PatternBouncerViewModel(
+ private val applicationContext: Context,
+ applicationScope: CoroutineScope,
+ private val interactor: BouncerInteractor,
+) : AuthMethodBouncerViewModel {
+
+ /** The number of columns in the dot grid. */
+ val columnCount = 3
+ /** The number of rows in the dot grid. */
+ val rowCount = 3
+
+ private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+ /** The dots that were selected by the user, in the order of selection. */
+ val selectedDots: StateFlow<List<PatternDotViewModel>> =
+ _selectedDots
+ .map { it.toList() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = emptyList(),
+ )
+
+ private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
+ /** The most-recently selected dot that the user selected. */
+ val currentDot: StateFlow<PatternDotViewModel?> = _currentDot.asStateFlow()
+
+ private val _dots = MutableStateFlow(defaultDots())
+ /** All dots on the grid. */
+ val dots: StateFlow<List<PatternDotViewModel>> = _dots.asStateFlow()
+
+ /** Notifies that the UI has been shown to the user. */
+ fun onShown() {
+ interactor.resetMessage()
+ }
+
+ /** Notifies that the user has started a drag gesture across the dot grid. */
+ fun onDragStart() {
+ interactor.clearMessage()
+ }
+
+ /**
+ * Notifies that the user is dragging across the dot grid.
+ *
+ * @param xPx The horizontal coordinate of the position of the user's pointer, in pixels.
+ * @param yPx The vertical coordinate of the position of the user's pointer, in pixels.
+ * @param containerSizePx The size of the container of the dot grid, in pixels. It's assumed
+ * that the dot grid is perfectly square such that width and height are equal.
+ * @param verticalOffsetPx How far down from `0` does the dot grid start on the display.
+ */
+ fun onDrag(xPx: Float, yPx: Float, containerSizePx: Int, verticalOffsetPx: Float) {
+ val cellWidthPx = containerSizePx / columnCount
+ val cellHeightPx = containerSizePx / rowCount
+
+ if (xPx < 0 || yPx < verticalOffsetPx) {
+ return
+ }
+
+ val dotColumn = (xPx / cellWidthPx).toInt()
+ val dotRow = ((yPx - verticalOffsetPx) / cellHeightPx).toInt()
+ if (dotColumn > columnCount - 1 || dotRow > rowCount - 1) {
+ return
+ }
+
+ val dotPixelX = dotColumn * cellWidthPx + cellWidthPx / 2
+ val dotPixelY = dotRow * cellHeightPx + cellHeightPx / 2 + verticalOffsetPx
+
+ val distance = sqrt((xPx - dotPixelX).pow(2) + (yPx - dotPixelY).pow(2))
+ val hitRadius = hitFactor * min(cellWidthPx, cellHeightPx) / 2
+ if (distance > hitRadius) {
+ return
+ }
+
+ val hitDot = dots.value.firstOrNull { dot -> dot.x == dotColumn && dot.y == dotRow }
+ if (hitDot != null && !_selectedDots.value.contains(hitDot)) {
+ val skippedOverDots =
+ currentDot.value?.let { previousDot ->
+ buildList {
+ var dot = previousDot
+ while (dot != hitDot) {
+ add(dot)
+ dot =
+ PatternDotViewModel(
+ x =
+ if (hitDot.x > dot.x) dot.x + 1
+ else if (hitDot.x < dot.x) dot.x - 1 else dot.x,
+ y =
+ if (hitDot.y > dot.y) dot.y + 1
+ else if (hitDot.y < dot.y) dot.y - 1 else dot.y,
+ )
+ }
+ }
+ }
+ ?: emptyList()
+
+ _selectedDots.value =
+ linkedSetOf<PatternDotViewModel>().apply {
+ addAll(_selectedDots.value)
+ addAll(skippedOverDots)
+ add(hitDot)
+ }
+ _currentDot.value = hitDot
+ }
+ }
+
+ /** Notifies that the user has ended the drag gesture across the dot grid. */
+ fun onDragEnd() {
+ interactor.authenticate(_selectedDots.value.map { it.toCoordinate() })
+
+ _dots.value = defaultDots()
+ _currentDot.value = null
+ _selectedDots.value = linkedSetOf()
+ }
+
+ private fun defaultDots(): List<PatternDotViewModel> {
+ return buildList {
+ (0 until columnCount).forEach { x ->
+ (0 until rowCount).forEach { y ->
+ add(
+ PatternDotViewModel(
+ x = x,
+ y = y,
+ )
+ )
+ }
+ }
+ }
+ }
+
+ private val hitFactor: Float by lazy {
+ val outValue = TypedValue()
+ applicationContext.resources.getValue(
+ com.android.internal.R.dimen.lock_pattern_dot_hit_factor,
+ outValue,
+ true
+ )
+ max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
+ }
+
+ companion object {
+ private const val MIN_DOT_HIT_FACTOR = 0.2f
+ }
+}
+
+data class PatternDotViewModel(
+ val x: Int,
+ val y: Int,
+) {
+ fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate {
+ return AuthenticationMethodModel.Pattern.PatternCoordinate(
+ x = x,
+ y = y,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
new file mode 100644
index 0000000..f9223cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.util.kotlin.pairwise
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Holds UI state and handles user input for the PIN code bouncer UI. */
+class PinBouncerViewModel(
+ private val applicationScope: CoroutineScope,
+ private val interactor: BouncerInteractor,
+) : AuthMethodBouncerViewModel {
+
+ private val entered = MutableStateFlow<List<Int>>(emptyList())
+ /**
+ * The length of the PIN digits that were input so far, two values are supplied the previous and
+ * the current.
+ */
+ val pinLengths: StateFlow<Pair<Int, Int>> =
+ entered
+ .pairwise()
+ .map { it.previousValue.size to it.newValue.size }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = 0 to 0,
+ )
+ private var resetPinJob: Job? = null
+
+ /** Notifies that the UI has been shown to the user. */
+ fun onShown() {
+ interactor.resetMessage()
+ }
+
+ /** Notifies that the user clicked on a PIN button with the given digit value. */
+ fun onPinButtonClicked(input: Int) {
+ resetPinJob?.cancel()
+ resetPinJob = null
+
+ if (entered.value.isEmpty()) {
+ interactor.clearMessage()
+ }
+
+ entered.value += input
+ }
+
+ /** Notifies that the user clicked the backspace button. */
+ fun onBackspaceButtonClicked() {
+ if (entered.value.isEmpty()) {
+ return
+ }
+
+ entered.value = entered.value.toMutableList().apply { removeLast() }
+ }
+
+ /** Notifies that the user long-pressed the backspace button. */
+ fun onBackspaceButtonLongPressed() {
+ resetPinJob?.cancel()
+ resetPinJob =
+ applicationScope.launch {
+ while (entered.value.isNotEmpty()) {
+ onBackspaceButtonClicked()
+ delay(BACKSPACE_LONG_PRESS_DELAY_MS)
+ }
+ }
+ }
+
+ /** Notifies that the user clicked the "enter" button. */
+ fun onAuthenticateButtonClicked() {
+ interactor.authenticate(entered.value)
+ entered.value = emptyList()
+ }
+
+ companion object {
+ @VisibleForTesting const val BACKSPACE_LONG_PRESS_DELAY_MS = 80L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5d6479e..fe54d34 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
-import com.android.systemui.ChooserSelector
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
import com.android.systemui.ScreenDecorations
@@ -84,12 +83,6 @@
service: BiometricNotificationService
): CoreStartable
- /** Inject into ChooserCoreStartable. */
- @Binds
- @IntoMap
- @ClassKey(ChooserSelector::class)
- abstract fun bindChooserSelector(sysui: ChooserSelector): CoreStartable
-
/** Inject into ClipboardListener. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 26fd086..340ed2e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -120,7 +120,12 @@
resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
// TODO(b/254512676): Tracking Bug
- @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks")
+ @JvmField
+ val LOCKSCREEN_CUSTOM_CLOCKS = resourceBooleanFlag(
+ 207,
+ R.bool.config_enableLockScreenCustomClocks,
+ "lockscreen_custom_clocks"
+ )
// TODO(b/275694445): Tracking Bug
@JvmField
@@ -235,7 +240,7 @@
/** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
// TODO(b/279794160): Tracking bug.
@JvmField
- val DELAY_BOUNCER = unreleasedFlag(235, "delay_bouncer")
+ val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@@ -660,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/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2a94b08..51af8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1501,13 +1501,17 @@
notifyFinishedGoingToSleep();
if (cameraGestureTriggered) {
-
// Just to make sure, make sure the device is awake.
mContext.getSystemService(PowerManager.class).wakeUp(SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_CAMERA_LAUNCH,
"com.android.systemui:CAMERA_GESTURE_PREVENT_LOCK");
setPendingLock(false);
mPendingReset = false;
+ mPowerGestureIntercepted = true;
+ if (DEBUG) {
+ Log.d(TAG, "cameraGestureTriggered=" + cameraGestureTriggered
+ + ",mPowerGestureIntercepted=" + mPowerGestureIntercepted);
+ }
}
if (mPendingReset) {
@@ -1706,7 +1710,13 @@
mAnimatingScreenOff = false;
cancelDoKeyguardLaterLocked();
cancelDoKeyguardForChildProfilesLocked();
- if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
+ if (cameraGestureTriggered) {
+ mPowerGestureIntercepted = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence
+ + ", mPowerGestureIntercepted = " + mPowerGestureIntercepted);
+ }
notifyStartedWakingUp();
}
mUiEventLogger.logWithInstanceIdAndPosition(
@@ -1933,12 +1943,19 @@
startKeyguardExitAnimation(0, 0);
}
+ mPowerGestureIntercepted = mUpdateMonitor.isSecureCameraLaunchedOverKeyguard();
+
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
&& mDeviceInteractive);
adjustStatusBarLocked();
}
+
+ if (DEBUG) {
+ Log.d(TAG, "isOccluded=" + isOccluded + ",mPowerGestureIntercepted="
+ + mPowerGestureIntercepted);
+ }
}
Trace.endSection();
}
@@ -3056,6 +3073,10 @@
flags |= StatusBarManager.DISABLE_RECENT;
}
+ if (mPowerGestureIntercepted) {
+ flags |= StatusBarManager.DISABLE_RECENT;
+ }
+
if (DEBUG) {
Log.d(TAG, "adjustStatusBarLocked: mShowing=" + mShowing + " mOccluded=" + mOccluded
+ " isSecure=" + isSecure() + " force=" + forceHideHomeRecentsButtons
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
new file mode 100644
index 0000000..4eb7d44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+import android.annotation.WorkerThread
+import android.content.ComponentCallbacks2
+import android.os.Trace
+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.dagger.qualifiers.Background
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.utils.GlobalWindowManager
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Releases cached resources on allocated by keyguard.
+ *
+ * We release most resources when device goes idle since that's the least likely time it'll cause
+ * jank during use. Idle in this case means after lockscreen -> AoD transition completes or when the
+ * device screen is turned off, depending on settings.
+ */
+@SysUISingleton
+class ResourceTrimmer
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val globalWindowManager: GlobalWindowManager,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+) : CoreStartable, WakefulnessLifecycle.Observer {
+
+ override fun start() {
+ Log.d(LOG_TAG, "Resource trimmer registered.")
+ applicationScope.launch(bgDispatcher) {
+ // We need to wait for the AoD transition (and animation) to complete.
+ // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
+ // signal. This is to make sure we don't clear font caches during animation which
+ // would jank and leave stale data in memory.
+ val isDozingFully =
+ keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
+ combine(
+ keyguardInteractor.wakefulnessModel.map { it.state },
+ keyguardInteractor.isDreaming,
+ isDozingFully,
+ ::Triple
+ )
+ .distinctUntilChanged()
+ .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
+ }
+ }
+
+ @WorkerThread
+ private fun onWakefulnessUpdated(
+ wakefulness: WakefulnessState,
+ isDreaming: Boolean,
+ isDozingFully: Boolean
+ ) {
+ if (DEBUG) {
+ Log.d(
+ LOG_TAG,
+ "Wakefulness: $wakefulness Dreaming: $isDreaming DozeAmount: $isDozingFully"
+ )
+ }
+ // There are three scenarios:
+ // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
+ // and dozeAmount == 0f
+ // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps
+ // to 1f
+ // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
+ // to 1f
+ val dozeDisabledAndScreenOff = wakefulness == WakefulnessState.ASLEEP && !isDreaming
+ val dozeEnabledAndDozeAnimationCompleted =
+ wakefulness == WakefulnessState.ASLEEP && isDreaming && isDozingFully
+ if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
+ Trace.beginSection("ResourceTrimmer#trimMemory")
+ Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
+ globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ Trace.endSection()
+ }
+ }
+
+ companion object {
+ private const val LOG_TAG = "ResourceTrimmer"
+ private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
+ }
+}
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/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 255556c..d7c039d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -87,6 +87,7 @@
KeyguardRepositoryModule.class,
KeyguardFaceAuthModule.class,
StartKeyguardTransitionModule.class,
+ ResourceTrimmerModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java
new file mode 100644
index 0000000..d693326
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/ResourceTrimmerModule.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dagger;
+
+import com.android.systemui.CoreStartable;
+import com.android.systemui.keyguard.ResourceTrimmer;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Binds {@link ResourceTrimmer} into {@link CoreStartable} set.
+ */
+@Module
+public abstract class ResourceTrimmerModule {
+
+ /** Bind ResourceTrimmer into CoreStarteables. */
+ @Binds
+ @IntoMap
+ @ClassKey(ResourceTrimmer.class)
+ public abstract CoreStartable bindResourceTrimmer(ResourceTrimmer resourceTrimmer);
+}
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/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index f2f1c48..867675b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -22,6 +22,7 @@
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.keyguard.shared.model.ActiveUnlockModel
import com.android.systemui.keyguard.shared.model.TrustManagedModel
import com.android.systemui.keyguard.shared.model.TrustModel
import com.android.systemui.user.data.repository.UserRepository
@@ -29,7 +30,6 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -45,8 +45,8 @@
/** Flow representing whether the current user is trusted. */
val isCurrentUserTrusted: Flow<Boolean>
- /** Flow representing whether active unlock is available for the current user. */
- val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
+ /** Flow representing whether active unlock is running for the current user. */
+ val isCurrentUserActiveUnlockRunning: Flow<Boolean>
/** Reports that whether trust is managed has changed for the current user. */
val isCurrentUserTrustManaged: StateFlow<Boolean>
@@ -62,6 +62,7 @@
private val logger: TrustRepositoryLogger,
) : TrustRepository {
private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+ private val activeUnlockRunningForUser = mutableMapOf<Int, ActiveUnlockModel>()
private val trustManagedForUser = mutableMapOf<Int, TrustManagedModel>()
private val trust =
@@ -87,6 +88,17 @@
override fun onEnabledTrustAgentsChanged(userId: Int) = Unit
+ override fun onIsActiveUnlockRunningChanged(
+ isRunning: Boolean,
+ userId: Int
+ ) {
+ trySendWithFailureLogging(
+ ActiveUnlockModel(isRunning, userId),
+ TrustRepositoryLogger.TAG,
+ "onActiveUnlockRunningChanged"
+ )
+ }
+
override fun onTrustManagedChanged(isTrustManaged: Boolean, userId: Int) {
logger.onTrustManagedChanged(isTrustManaged, userId)
trySendWithFailureLogging(
@@ -95,11 +107,6 @@
"onTrustManagedChanged"
)
}
-
- override fun onIsActiveUnlockRunningChanged(
- isRunning: Boolean,
- userId: Int
- ) = Unit
}
trustManager.registerTrustListener(callback)
logger.trustListenerRegistered()
@@ -114,6 +121,10 @@
latestTrustModelForUser[it.userId] = it
logger.trustModelEmitted(it)
}
+ is ActiveUnlockModel -> {
+ activeUnlockRunningForUser[it.userId] = it
+ logger.activeUnlockModelEmitted(it)
+ }
is TrustManagedModel -> {
trustManagedForUser[it.userId] = it
logger.trustManagedModelEmitted(it)
@@ -122,8 +133,17 @@
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
- // TODO: Implement based on TrustManager callback b/267322286
- override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
+ override val isCurrentUserActiveUnlockRunning: Flow<Boolean> =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { activeUnlockRunningForUser[it.second.id]?.isRunning ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserActiveUnlockRunning(it) }
+ .onStart {
+ emit(
+ activeUnlockRunningForUser[userRepository.getSelectedUserInfo().id]?.isRunning
+ ?: false
+ )
+ }
override val isCurrentUserTrustManaged: StateFlow<Boolean>
get() =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index 110bcd7..233146a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -31,6 +31,7 @@
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -43,11 +44,14 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.policy.KeyguardStateController
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import javax.inject.Inject
/**
@@ -58,18 +62,19 @@
class PrimaryBouncerInteractor
@Inject
constructor(
- private val repository: KeyguardBouncerRepository,
- private val primaryBouncerView: BouncerView,
- @Main private val mainHandler: Handler,
- private val keyguardStateController: KeyguardStateController,
- private val keyguardSecurityModel: KeyguardSecurityModel,
- private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
- private val falsingCollector: FalsingCollector,
- private val dismissCallbackRegistry: DismissCallbackRegistry,
- private val context: Context,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val trustRepository: TrustRepository,
- private val featureFlags: FeatureFlags,
+ private val repository: KeyguardBouncerRepository,
+ private val primaryBouncerView: BouncerView,
+ @Main private val mainHandler: Handler,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSecurityModel: KeyguardSecurityModel,
+ private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
+ private val falsingCollector: FalsingCollector,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val context: Context,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val trustRepository: TrustRepository,
+ private val featureFlags: FeatureFlags,
+ @Application private val applicationScope: CoroutineScope,
) {
private val passiveAuthBouncerDelay = context.resources.getInteger(
R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -104,6 +109,7 @@
/** Allow for interaction when just about fully visible */
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+ private var currentUserActiveUnlockRunning = false
/** This callback needs to be a class field so it does not get garbage collected. */
val keyguardUpdateMonitorCallback =
@@ -122,6 +128,13 @@
init {
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ if (featureFlags.isEnabled(Flags.DELAY_BOUNCER)) {
+ applicationScope.launch {
+ trustRepository.isCurrentUserActiveUnlockRunning.collect {
+ currentUserActiveUnlockRunning = it
+ }
+ }
+ }
}
// TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -377,8 +390,9 @@
private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
- val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
+ val canRunActiveUnlock = currentUserActiveUnlockRunning &&
keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
+
return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
!needsFullscreenBouncer() &&
(canRunFaceAuth || canRunActiveUnlock)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt
new file mode 100644
index 0000000..7309072
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ActiveUnlockModel.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+/** Represents the active unlock state */
+data class ActiveUnlockModel(
+ /** If true, the system believes active unlock is available and can be usd to unlock. */
+ val isRunning: Boolean,
+ /** The user, for which active unlock may be running. */
+ val userId: Int,
+)
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/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index fa42114..5079487 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -43,6 +43,7 @@
import android.net.Uri
import android.os.Parcelable
import android.os.Process
+import android.os.RemoteException
import android.os.UserHandle
import android.provider.Settings
import android.service.notification.StatusBarNotification
@@ -52,6 +53,7 @@
import android.util.Pair as APair
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -137,6 +139,8 @@
expiryTimeMs = 0,
)
+const val MEDIA_TITLE_ERROR_MESSAGE = "Invalid media data: title is null or blank."
+
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.isMediaNotification()
}
@@ -181,6 +185,7 @@
private val logger: MediaUiEventLogger,
private val smartspaceManager: SmartspaceManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val statusBarService: IStatusBarService,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -252,6 +257,7 @@
mediaFlags: MediaFlags,
logger: MediaUiEventLogger,
smartspaceManager: SmartspaceManager,
+ statusBarService: IStatusBarService,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) : this(
context,
@@ -277,6 +283,7 @@
logger,
smartspaceManager,
keyguardUpdateMonitor,
+ statusBarService,
)
private val appChangeReceiver =
@@ -378,21 +385,21 @@
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
if (useQsMediaPlayer && isMediaNotification(sbn)) {
- var logEvent = false
+ var isNewlyActiveEntry = false
Assert.isMainThread()
val oldKey = findExistingEntry(key, sbn.packageName)
if (oldKey == null) {
val instanceId = logger.getNewInstanceId()
val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
mediaEntries.put(key, temp)
- logEvent = true
+ isNewlyActiveEntry = true
} else if (oldKey != key) {
// Resume -> active conversion; move to new key
val oldData = mediaEntries.remove(oldKey)!!
- logEvent = true
+ isNewlyActiveEntry = true
mediaEntries.put(key, oldData)
}
- loadMediaData(key, sbn, oldKey, logEvent)
+ loadMediaData(key, sbn, oldKey, isNewlyActiveEntry)
} else {
onNotificationRemoved(key)
}
@@ -475,9 +482,9 @@
key: String,
sbn: StatusBarNotification,
oldKey: String?,
- logEvent: Boolean = false
+ isNewlyActiveEntry: Boolean = false,
) {
- backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, logEvent) }
+ backgroundExecutor.execute { loadMediaDataInBg(key, sbn, oldKey, isNewlyActiveEntry) }
}
/** Add a listener for changes in this class */
@@ -601,9 +608,11 @@
}
}
- private fun removeEntry(key: String) {
+ private fun removeEntry(key: String, logEvent: Boolean = true) {
mediaEntries.remove(key)?.let {
- logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ if (logEvent) {
+ logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId)
+ }
}
notifyMediaDataRemoved(key)
}
@@ -751,7 +760,7 @@
key: String,
sbn: StatusBarNotification,
oldKey: String?,
- logEvent: Boolean = false
+ isNewlyActiveEntry: Boolean = false,
) {
val token =
sbn.notification.extras.getParcelable(
@@ -772,6 +781,42 @@
)
?: getAppInfoFromPackage(sbn.packageName)
+ // App name
+ val appName = getAppName(sbn, appInfo)
+
+ // Song name
+ var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
+ if (song == null) {
+ song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
+ }
+ if (song == null) {
+ song = HybridGroupManager.resolveTitle(notif)
+ }
+ if (song.isNullOrBlank()) {
+ if (mediaFlags.isMediaTitleRequired(sbn.packageName, sbn.user)) {
+ // App is required to provide a title: cancel the underlying notification
+ try {
+ statusBarService.onNotificationError(
+ sbn.packageName,
+ sbn.tag,
+ sbn.id,
+ sbn.uid,
+ sbn.initialPid,
+ MEDIA_TITLE_ERROR_MESSAGE,
+ sbn.user.identifier
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "cancelNotification failed: $e")
+ }
+ // Only add log for media removed if active media is updated with invalid title.
+ foregroundExecutor.execute { removeEntry(key, !isNewlyActiveEntry) }
+ return
+ } else {
+ // For apps that don't have the title requirement yet, add a placeholder
+ song = context.getString(R.string.controls_media_empty_title, appName)
+ }
+ }
+
// Album art
var artworkBitmap = metadata?.let { loadBitmapFromUri(it) }
if (artworkBitmap == null) {
@@ -787,21 +832,9 @@
Icon.createWithBitmap(artworkBitmap)
}
- // App name
- val appName = getAppName(sbn, appInfo)
-
// App Icon
val smallIcon = sbn.notification.smallIcon
- // Song name
- var song: CharSequence? = metadata?.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
- if (song == null) {
- song = metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
- }
- if (song == null) {
- song = HybridGroupManager.resolveTitle(notif)
- }
-
// Explicit Indicator
var isExplicit = false
if (mediaFlags.isExplicitIndicatorEnabled()) {
@@ -873,7 +906,7 @@
val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId()
val appUid = appInfo?.uid ?: Process.INVALID_UID
- if (logEvent) {
+ if (isNewlyActiveEntry) {
logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
} else if (playbackLocation != currentEntry?.playbackLocation) {
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/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 9bc66f6..3751c60 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -64,4 +64,9 @@
/** Check whether we allow remote media to generate resume controls */
fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+
+ /** Check whether app is required to provide a non-empty media title */
+ fun isMediaTitleRequired(packageName: String, user: UserHandle): Boolean {
+ return StatusBarManager.isMediaTitleRequiredForApp(packageName, user)
+ }
}
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 770e4df..0a5b4b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -101,6 +101,7 @@
private Button mAppButton;
private int mListMaxHeight;
private int mItemHeight;
+ private int mListPaddingTop;
private WallpaperColors mWallpaperColors;
private boolean mShouldLaunchLeBroadcastDialog;
private boolean mIsLeBroadcastCallbackRegistered;
@@ -111,7 +112,8 @@
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
- int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
+ int totalItemsHeight = mAdapter.getItemCount() * mItemHeight
+ + mListPaddingTop;
int correctHeight = Math.min(totalItemsHeight, mListMaxHeight);
// Set max height for list
if (correctHeight != params.height) {
@@ -220,6 +222,8 @@
R.dimen.media_output_dialog_list_max_height);
mItemHeight = context.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_list_item_height);
+ mListPaddingTop = mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_list_padding_top);
mExecutor = Executors.newSingleThreadExecutor();
}
@@ -266,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/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index cdd00f9..a1e9995 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -29,6 +29,7 @@
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import java.util.Optional
import javax.inject.Inject
@@ -49,7 +50,8 @@
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -61,7 +63,7 @@
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags)
+ powerExemptionManager, keyGuardManager, featureFlags, userTracker)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 822644b..cc75478 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -49,6 +49,7 @@
import android.media.NearbyDevice;
import android.media.RoutingSessionInfo;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.IBinder;
@@ -82,10 +83,10 @@
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;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -130,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;
@@ -165,6 +164,7 @@
private float mInactiveRadius;
private float mActiveRadius;
private FeatureFlags mFeatureFlags;
+ private UserTracker mUserTracker;
public enum BroadcastNotifyDialog {
ACTION_FIRST_LAUNCH,
@@ -181,7 +181,8 @@
AudioManager audioManager,
PowerExemptionManager powerExemptionManager,
KeyguardManager keyGuardManager,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ UserTracker userTracker) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -192,6 +193,7 @@
mPowerExemptionManager = powerExemptionManager;
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
+ mUserTracker = userTracker;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -224,7 +226,6 @@
void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaDevices.clear();
mMediaItemList.clear();
}
mNearbyDeviceInfoMap.clear();
@@ -232,16 +233,13 @@
mNearbyMediaDevicesManager.registerNearbyDevicesCallback(this);
}
if (!TextUtils.isEmpty(mPackageName)) {
- for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
- if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
- mMediaController = controller;
- mMediaController.unregisterCallback(mCb);
- if (mMediaController.getPlaybackState() != null) {
- mCurrentState = mMediaController.getPlaybackState().getState();
- }
- mMediaController.registerCallback(mCb);
- break;
+ mMediaController = getMediaController();
+ if (mMediaController != null) {
+ mMediaController.unregisterCallback(mCb);
+ if (mMediaController.getPlaybackState() != null) {
+ mCurrentState = mMediaController.getPlaybackState().getState();
}
+ mMediaController.registerCallback(mCb);
}
}
if (mMediaController == null) {
@@ -275,7 +273,6 @@
mLocalMediaManager.stopScan();
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
- mMediaDevices.clear();
mMediaItemList.clear();
}
if (mNearbyMediaDevicesManager != null) {
@@ -284,12 +281,31 @@
mNearbyDeviceInfoMap.clear();
}
+ private MediaController getMediaController() {
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ MediaSession.Token token = notification.extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token.class);
+ return new MediaController(mContext, token);
+ }
+ }
+ for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null,
+ mUserTracker.getUserHandle())) {
+ if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
+ return controller;
+ }
+ }
+ return null;
+ }
+
@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) {
@@ -304,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
@@ -319,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);
}
/**
@@ -342,7 +350,6 @@
}
try {
synchronized (mMediaDevicesLock) {
- mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice);
mMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
@@ -547,7 +554,7 @@
void refreshDataSetIfNeeded() {
if (mNeedRefresh) {
- buildMediaDevices(mCachedMediaDevices);
+ buildMediaItems(mCachedMediaDevices);
mCallback.onDeviceListChanged();
mNeedRefresh = false;
}
@@ -597,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());
}
@@ -789,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();
@@ -845,10 +774,6 @@
});
}
- Collection<MediaDevice> getMediaDevices() {
- return mMediaDevices;
- }
-
public List<MediaItem> getMediaItemList() {
return mMediaItemList;
}
@@ -935,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;
}
}
}
@@ -1011,7 +928,8 @@
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags,
+ mUserTracker);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
dialog.show();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 7dbf876..8024886 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -32,6 +32,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
@@ -51,7 +52,8 @@
private val audioManager: AudioManager,
private val powerExemptionManager: PowerExemptionManager,
private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val userTracker: UserTracker
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -67,7 +69,7 @@
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags)
+ powerExemptionManager, keyGuardManager, featureFlags, userTracker)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index f7e7366..abeb5af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -153,14 +153,16 @@
@VisibleForTesting
/** Should be accessible only to the main thread. */
final Map<Integer, TelephonyDisplayInfo> mSubIdTelephonyDisplayInfoMap = new HashMap<>();
+ @VisibleForTesting
+ /** Should be accessible only to the main thread. */
+ final Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
+ @VisibleForTesting
+ /** Should be accessible only to the main thread. */
+ final Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
private WifiManager mWifiManager;
private Context mContext;
private SubscriptionManager mSubscriptionManager;
- /** Should be accessible only to the main thread. */
- private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
- /** Should be accessible only to the main thread. */
- private Map<Integer, TelephonyCallback> mSubIdTelephonyCallbackMap = new HashMap<>();
private TelephonyManager mTelephonyManager;
private ConnectivityManager mConnectivityManager;
private CarrierConfigTracker mCarrierConfigTracker;
@@ -320,6 +322,9 @@
Log.e(TAG, "Unexpected null telephony call back for Sub " + tm.getSubscriptionId());
}
}
+ mSubIdTelephonyManagerMap.clear();
+ mSubIdTelephonyCallbackMap.clear();
+ mSubIdTelephonyDisplayInfoMap.clear();
mSubscriptionManager.removeOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
mAccessPointController.removeAccessPointCallback(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 0ea2570..12420ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -186,7 +186,7 @@
private boolean mPowerPluggedInDock;
private boolean mPowerCharged;
- private boolean mBatteryOverheated;
+ private boolean mBatteryDefender;
private boolean mEnableBatteryDefender;
private boolean mIncompatibleCharger;
private int mChargingSpeed;
@@ -921,7 +921,7 @@
*/
protected String computePowerIndication() {
int chargingId;
- if (mBatteryOverheated) {
+ if (mBatteryDefender) {
chargingId = R.string.keyguard_plugged_in_charging_limited;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
@@ -1093,9 +1093,9 @@
mChargingSpeed = status.getChargingSpeed(mContext);
mBatteryLevel = status.level;
mBatteryPresent = status.present;
- mBatteryOverheated = status.isOverheated();
+ mBatteryDefender = status.isBatteryDefender();
// when the battery is overheated, device doesn't charge so only guard on pluggedIn:
- mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
+ mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn();
mIncompatibleCharger = status.incompatibleCharger.orElse(false);
try {
mChargingTimeRemaining = mPowerPluggedIn
@@ -1106,7 +1106,7 @@
}
mKeyguardLogger.logRefreshBatteryInfo(isChargingOrFull, mPowerPluggedIn, mBatteryLevel,
- mBatteryOverheated);
+ mBatteryDefender);
updateDeviceEntryIndication(!wasPluggedIn && mPowerPluggedInWired);
}
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/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index d10d7cf..3d16591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -156,7 +156,7 @@
default void onWirelessChargingChanged(boolean isWirlessCharging) {
}
- default void onIsOverheatedChanged(boolean isOverheated) {
+ default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 1e63b2a..e69d86c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,9 +16,9 @@
package com.android.systemui.statusbar.policy;
-import static android.os.BatteryManager.BATTERY_HEALTH_OVERHEAT;
-import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
-import static android.os.BatteryManager.EXTRA_HEALTH;
+import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
+import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
@@ -94,7 +94,7 @@
protected boolean mPowerSave;
private boolean mAodPowerSave;
private boolean mWirelessCharging;
- private boolean mIsOverheated = false;
+ private boolean mIsBatteryDefender = false;
private boolean mTestMode = false;
@VisibleForTesting
boolean mHasReceivedBattery = false;
@@ -163,7 +163,7 @@
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
- pw.print(" mIsOverheated="); pw.println(mIsOverheated);
+ pw.print(" mIsBatteryDefender="); pw.println(mIsBatteryDefender);
pw.print(" mPowerSave="); pw.println(mPowerSave);
pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
@@ -197,7 +197,7 @@
cb.onPowerSaveChanged(mPowerSave);
cb.onBatteryUnknownStateChanged(mStateUnknown);
cb.onWirelessChargingChanged(mWirelessCharging);
- cb.onIsOverheatedChanged(mIsOverheated);
+ cb.onIsBatteryDefenderChanged(mIsBatteryDefender);
}
@Override
@@ -236,11 +236,11 @@
fireBatteryUnknownStateChanged();
}
- int batteryHealth = intent.getIntExtra(EXTRA_HEALTH, BATTERY_HEALTH_UNKNOWN);
- boolean isOverheated = batteryHealth == BATTERY_HEALTH_OVERHEAT;
- if (isOverheated != mIsOverheated) {
- mIsOverheated = isOverheated;
- fireIsOverheatedChanged();
+ int chargingStatus = intent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+ boolean isBatteryDefender = chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+ if (isBatteryDefender != mIsBatteryDefender) {
+ mIsBatteryDefender = isBatteryDefender;
+ fireIsBatteryDefenderChanged();
}
fireBatteryLevelChanged();
@@ -313,8 +313,8 @@
return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
- public boolean isOverheated() {
- return mIsOverheated;
+ public boolean isBatteryDefender() {
+ return mIsBatteryDefender;
}
@Override
@@ -428,11 +428,11 @@
}
}
- private void fireIsOverheatedChanged() {
+ private void fireIsBatteryDefenderChanged() {
synchronized (mChangeCallbacks) {
final int n = mChangeCallbacks.size();
for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onIsOverheatedChanged(mIsOverheated);
+ mChangeCallbacks.get(i).onIsBatteryDefenderChanged(mIsBatteryDefender);
}
}
}
@@ -447,7 +447,7 @@
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
String present = args.getString("present");
- String overheated = args.getString("overheated");
+ String defender = args.getString("defender");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -462,9 +462,9 @@
mStateUnknown = !present.equals("true");
fireBatteryUnknownStateChanged();
}
- if (overheated != null) {
- mIsOverheated = overheated.equals("true");
- fireIsOverheatedChanged();
+ if (defender != null) {
+ mIsBatteryDefender = defender.equals("true");
+ fireIsBatteryDefenderChanged();
}
fireBatteryLevelChanged();
}
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/src/com/android/systemui/utils/GlobalWindowManager.kt b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
new file mode 100644
index 0000000..038fddc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/utils/GlobalWindowManager.kt
@@ -0,0 +1,16 @@
+package com.android.systemui.utils
+
+import android.view.WindowManagerGlobal
+import javax.inject.Inject
+
+/** Interface to talk to [WindowManagerGlobal] */
+class GlobalWindowManager @Inject constructor() {
+ /**
+ * Sends a trim memory command to [WindowManagerGlobal].
+ *
+ * @param level One of levels from [ComponentCallbacks2] starting with TRIM_
+ */
+ fun trimMemory(level: Int) {
+ WindowManagerGlobal.getInstance().trimMemory(level)
+ }
+}
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/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index a35e5b5..d4522d0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -27,6 +27,8 @@
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.text.Editable;
+import android.text.TextWatcher;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -93,16 +95,16 @@
}
@Test
- public void testSetMessage_AnnounceForAccessibility() {
- ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
- when(mKeyguardMessageArea.getText()).thenReturn("abc");
- mMessageAreaController.setMessage("abc");
+ public void textChanged_AnnounceForAccessibility() {
+ ArgumentCaptor<TextWatcher> textWatcherArgumentCaptor = ArgumentCaptor.forClass(
+ TextWatcher.class);
+ mMessageAreaController.onViewAttached();
+ verify(mKeyguardMessageArea).addTextChangedListener(textWatcherArgumentCaptor.capture());
- verify(mKeyguardMessageArea).setMessage("abc", /* animate= */ true);
+ textWatcherArgumentCaptor.getValue().afterTextChanged(
+ Editable.Factory.getInstance().newEditable("abc"));
verify(mKeyguardMessageArea).removeCallbacks(any(Runnable.class));
- verify(mKeyguardMessageArea).postDelayed(argumentCaptor.capture(), anyLong());
- argumentCaptor.getValue().run();
- verify(mKeyguardMessageArea).announceForAccessibility("abc");
+ verify(mKeyguardMessageArea).postDelayed(any(Runnable.class), anyLong());
}
@Test
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/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 70476aa..d3b4190 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -152,9 +152,15 @@
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
- verify(deleteButton).visibility = View.INVISIBLE
+ verify(deleteButton).visibility = View.VISIBLE
verify(enterButton).visibility = View.VISIBLE
verify(passwordTextView).setUsePinShapes(true)
- verify(passwordTextView).setIsPinHinting(true)
+ verify(passwordTextView).setIsPinHinting(false)
+ }
+
+ @Test
+ fun handleLockout_readsNumberOfErrorAttempts() {
+ pinViewController.handleAttemptLockout(0)
+ verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt())
}
}
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/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d867df5..2f72cb9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -3088,7 +3088,8 @@
private Intent getBatteryIntent() {
return new Intent(Intent.ACTION_BATTERY_CHANGED).putExtra(
- BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ BatteryManager.EXTRA_CHARGING_STATUS,
+ BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE);
}
private class TestableKeyguardUpdateMonitor extends KeyguardUpdateMonitor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
deleted file mode 100644
index babbe45..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt
+++ /dev/null
@@ -1,215 +0,0 @@
-package com.android.systemui
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.pm.PackageManager
-import android.content.pm.UserInfo
-import android.content.res.Resources
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flag
-import com.android.systemui.flags.FlagListenable
-import com.android.systemui.flags.Flags
-import com.android.systemui.flags.ReleasedFlag
-import com.android.systemui.flags.UnreleasedFlag
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.test.TestCoroutineDispatcher
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ChooserSelectorTest : SysuiTestCase() {
-
- private val flagListener = kotlinArgumentCaptor<FlagListenable.Listener>()
-
- private val testDispatcher = TestCoroutineDispatcher()
- private val testScope = CoroutineScope(testDispatcher)
-
- private lateinit var chooserSelector: ChooserSelector
-
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockProfileContext: Context
- @Mock private lateinit var mockUserTracker: UserTracker
- @Mock private lateinit var mockPackageManager: PackageManager
- @Mock private lateinit var mockResources: Resources
- @Mock private lateinit var mockFeatureFlags: FeatureFlags
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- whenever(mockContext.createContextAsUser(any(), anyInt())).thenReturn(mockProfileContext)
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(mockProfileContext.packageManager).thenReturn(mockPackageManager)
- whenever(mockResources.getString(anyInt())).thenReturn(
- ComponentName("TestPackage", "TestClass").flattenToString())
- whenever(mockUserTracker.userProfiles).thenReturn(listOf(UserInfo(), UserInfo()))
-
- chooserSelector = ChooserSelector(
- mockContext,
- mockUserTracker,
- mockFeatureFlags,
- testScope,
- testDispatcher,
- )
- }
-
- @After
- fun tearDown() {
- testDispatcher.cleanupTestCoroutines()
- }
-
- @Test
- fun initialize_registersFlagListenerUntilScopeCancelled() {
- // Arrange
-
- // Act
- chooserSelector.start()
-
- // Assert
- verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
- flagListener.capture(),
- )
- verify(mockFeatureFlags, never()).removeListener(any())
-
- // Act
- testScope.cancel()
-
- // Assert
- verify(mockFeatureFlags).removeListener(eq(flagListener.value))
- }
-
- @Test
- fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
- // Arrange
- setFlagMock(true)
-
- // Act
- chooserSelector.start()
-
- // Assert
- verify(mockPackageManager, times(2)).setComponentEnabledSetting(
- eq(ComponentName("TestPackage", "TestClass")),
- eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- anyInt(),
- )
- }
-
- @Test
- fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
- // Arrange
- setFlagMock(false)
-
- // Act
- chooserSelector.start()
-
- // Assert
- verify(mockPackageManager, times(2)).setComponentEnabledSetting(
- eq(ComponentName("TestPackage", "TestClass")),
- eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- anyInt(),
- )
- }
-
- @Test
- fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
- // Arrange
- setFlagMock(false)
- chooserSelector.start()
- verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
- flagListener.capture(),
- )
- verify(mockPackageManager, never()).setComponentEnabledSetting(
- any(),
- eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- anyInt(),
- )
-
- // Act
- setFlagMock(true)
- flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
-
- // Assert
- verify(mockPackageManager, times(2)).setComponentEnabledSetting(
- eq(ComponentName("TestPackage", "TestClass")),
- eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
- anyInt(),
- )
- }
-
- @Test
- fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
- // Arrange
- setFlagMock(true)
- chooserSelector.start()
- verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
- flagListener.capture(),
- )
- verify(mockPackageManager, never()).setComponentEnabledSetting(
- any(),
- eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- anyInt(),
- )
-
- // Act
- setFlagMock(false)
- flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.name))
-
- // Assert
- verify(mockPackageManager, times(2)).setComponentEnabledSetting(
- eq(ComponentName("TestPackage", "TestClass")),
- eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
- anyInt(),
- )
- }
-
- @Test
- fun doesNothing_whenAnotherFlagChanges() {
- // Arrange
- setFlagMock(false)
- chooserSelector.start()
- verify(mockFeatureFlags).addListener(
- eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED),
- flagListener.capture(),
- )
- clearInvocations(mockPackageManager)
-
- // Act
- flagListener.value.onFlagChanged(TestFlagEvent("other flag"))
-
- // Assert
- verifyZeroInteractions(mockPackageManager)
- }
-
- private fun setFlagMock(enabled: Boolean) {
- whenever(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(enabled)
- whenever(mockFeatureFlags.isEnabled(any<ReleasedFlag>())).thenReturn(enabled)
- }
-
- private class TestFlagEvent(override val flagName: String) : FlagListenable.FlagEvent {
- override fun requestNoRestart() {}
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
index eff8c01..f64db78f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IWindowMagnificationConnectionTest.java
@@ -80,6 +80,8 @@
private OverviewProxyService mOverviewProxyService;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private AccessibilityLogger mA11yLogger;
private IWindowMagnificationConnection mIWindowMagnificationConnection;
private WindowMagnification mWindowMagnification;
@@ -97,7 +99,7 @@
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue,
mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
- mDisplayTracker, getContext().getSystemService(DisplayManager.class));
+ mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class));
mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index 086f628..38ecec0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent;
import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -89,6 +90,8 @@
private WindowMagnificationController mWindowMagnificationController;
@Mock
private MagnificationSettingsController mMagnificationSettingsController;
+ @Mock
+ private AccessibilityLogger mA11yLogger;
@Before
public void setUp() throws Exception {
@@ -103,11 +106,22 @@
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+ doAnswer(invocation -> {
+ mWindowMagnification.mMagnificationSettingsControllerCallback
+ .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ true);
+ return null;
+ }).when(mMagnificationSettingsController).showMagnificationSettings();
+ doAnswer(invocation -> {
+ mWindowMagnification.mMagnificationSettingsControllerCallback
+ .onSettingsPanelVisibilityChanged(TEST_DISPLAY, /* shown= */ false);
+ return null;
+ }).when(mMagnificationSettingsController).closeMagnificationSettings();
+
mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
mWindowMagnification = new WindowMagnification(getContext(),
getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController,
mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
- getContext().getSystemService(DisplayManager.class));
+ getContext().getSystemService(DisplayManager.class), mA11yLogger);
mWindowMagnification.mMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mWindowMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
@@ -185,6 +199,8 @@
waitForIdleSync();
verify(mMagnificationSettingsController).showMagnificationSettings();
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED));
}
@Test
@@ -195,6 +211,8 @@
waitForIdleSync();
verify(mWindowMagnificationController).changeMagnificationSize(eq(index));
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED));
}
@Test
@@ -214,6 +232,16 @@
waitForIdleSync();
verify(mWindowMagnificationController).setEditMagnifierSizeMode(eq(true));
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED));
+
+ mWindowMagnification.mMagnificationSettingsControllerCallback.onEditMagnifierSizeMode(
+ TEST_DISPLAY, /* enable= */ false);
+ waitForIdleSync();
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_ACTIVATED));
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED));
}
@Test
@@ -239,6 +267,8 @@
waitForIdleSync();
verify(mMagnificationSettingsController).closeMagnificationSettings();
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED));
verify(mConnectionCallback).onChangeMagnificationMode(eq(TEST_DISPLAY),
eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
}
@@ -268,6 +298,8 @@
waitForIdleSync();
verify(mWindowMagnificationController).updateDragHandleResourcesIfNeeded(eq(shown));
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 2e62beb..44c9905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -19,9 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -37,10 +37,10 @@
class AuthenticationInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
- private val repository: AuthenticationRepository = AuthenticationRepositoryImpl()
+ private val utils = SceneTestUtils(this, testScope)
+ private val repository: AuthenticationRepository = utils.authenticationRepository()
private val underTest =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
+ utils.authenticationInteractor(
repository = repository,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index eb7d9c3..c84efac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -85,9 +85,9 @@
}
@Test
- fun contentDescription_estimateAndOverheated() {
+ fun contentDescription_estimateAndBatteryDefender() {
mBatteryMeterView.onBatteryLevelChanged(17, false)
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
@@ -103,9 +103,9 @@
}
@Test
- fun contentDescription_overheated() {
+ fun contentDescription_batteryDefender() {
mBatteryMeterView.onBatteryLevelChanged(90, false)
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
context.getString(R.string.accessibility_battery_level_charging_paused, 90)
@@ -155,14 +155,14 @@
@Test
fun contentDescription_manyUpdates_alwaysUpdated() {
- // Overheated
+ // BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
context.getString(R.string.accessibility_battery_level_charging_paused, 90)
)
- // Overheated & estimate
+ // BatteryDefender & estimate
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
mBatteryMeterView.updatePercentText()
@@ -175,7 +175,7 @@
)
// Just estimate
- mBatteryMeterView.onIsOverheatedChanged(false)
+ mBatteryMeterView.onIsBatteryDefenderChanged(false)
assertThat(mBatteryMeterView.contentDescription).isEqualTo(
context.getString(
R.string.accessibility_battery_level_with_estimate,
@@ -198,35 +198,35 @@
}
@Test
- fun isOverheatedChanged_true_drawableGetsTrue() {
+ fun isBatteryDefenderChanged_true_drawableGetsTrue() {
mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
assertThat(drawable.displayShield).isTrue()
}
@Test
- fun isOverheatedChanged_false_drawableGetsFalse() {
+ fun isBatteryDefenderChanged_false_drawableGetsFalse() {
mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
// Start as true
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
// Update to false
- mBatteryMeterView.onIsOverheatedChanged(false)
+ mBatteryMeterView.onIsBatteryDefenderChanged(false)
assertThat(drawable.displayShield).isFalse()
}
@Test
- fun isOverheatedChanged_true_featureflagOff_drawableGetsFalse() {
+ fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse() {
mBatteryMeterView.setDisplayShieldEnabled(false)
val drawable = getBatteryDrawable()
- mBatteryMeterView.onIsOverheatedChanged(true)
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
assertThat(drawable.displayShield).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 7531cb4..ffad326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -24,13 +24,14 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -95,8 +96,9 @@
mock(DismissCallbackRegistry::class.java),
context,
mKeyguardUpdateMonitor,
- mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeTrustRepository(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ testScope.backgroundScope,
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 7dd376e..730f89d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -19,13 +19,9 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -44,23 +40,16 @@
class BouncerInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
private val authenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
)
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
+ private val sceneInteractor = utils.sceneInteractor()
private val underTest =
- BouncerInteractor(
- applicationScope = testScope.backgroundScope,
- applicationContext = context,
- repository = BouncerRepository(),
+ utils.bouncerInteractor(
authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- containerName = "container1",
)
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
new file mode 100644
index 0000000..954e67d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class BouncerViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
+ )
+ private val underTest =
+ utils.bouncerViewModel(
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = utils.sceneInteractor(),
+ )
+ )
+
+ @Test
+ fun authMethod_nonNullForSecureMethods_nullForNotSecureMethods() =
+ testScope.runTest {
+ val authMethodViewModel: AuthMethodBouncerViewModel? by
+ collectLastValue(underTest.authMethod)
+ authMethodsToTest().forEach { authMethod ->
+ authenticationInteractor.setAuthenticationMethod(authMethod)
+
+ if (authMethod.isSecure) {
+ assertThat(authMethodViewModel).isNotNull()
+ } else {
+ assertThat(authMethodViewModel).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun authMethod_reusesInstances() =
+ testScope.runTest {
+ val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>()
+ val authMethodViewModel: AuthMethodBouncerViewModel? by
+ collectLastValue(underTest.authMethod)
+ // First pass, populate our "seen" map:
+ authMethodsToTest().forEach { authMethod ->
+ authenticationInteractor.setAuthenticationMethod(authMethod)
+ authMethodViewModel?.let { seen[authMethod] = it }
+ }
+
+ // Second pass, assert same instances are reused:
+ authMethodsToTest().forEach { authMethod ->
+ authenticationInteractor.setAuthenticationMethod(authMethod)
+ authMethodViewModel?.let { assertThat(it).isSameInstanceAs(seen[authMethod]) }
+ }
+ }
+
+ @Test
+ fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() {
+ assertThat(authMethodsToTest().map { it::class }.toSet())
+ .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
+ }
+
+ private fun authMethodsToTest(): List<AuthenticationMethodModel> {
+ return listOf(
+ AuthenticationMethodModel.None,
+ AuthenticationMethodModel.Swipe,
+ AuthenticationMethodModel.PIN(1234),
+ AuthenticationMethodModel.Password("password"),
+ AuthenticationMethodModel.Pattern(
+ listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1))
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
new file mode 100644
index 0000000..e48b638
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -0,0 +1,198 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PasswordBouncerViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
+ )
+ private val sceneInteractor = utils.sceneInteractor()
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ private val bouncerViewModel =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ )
+ private val underTest =
+ PasswordBouncerViewModel(
+ interactor = bouncerInteractor,
+ )
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.keyguard_enter_your_password, ENTER_YOUR_PASSWORD)
+ overrideResource(R.string.kg_wrong_password, WRONG_PASSWORD)
+ }
+
+ @Test
+ fun onShown() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val password by collectLastValue(underTest.password)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.onShown()
+
+ assertThat(message).isEqualTo(ENTER_YOUR_PASSWORD)
+ assertThat(password).isEqualTo("")
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onPasswordInputChanged() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val password by collectLastValue(underTest.password)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+
+ underTest.onPasswordInputChanged("password")
+
+ assertThat(message).isEmpty()
+ assertThat(password).isEqualTo("password")
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onAuthenticateKeyPressed_whenCorrect() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPasswordInputChanged("password")
+
+ underTest.onAuthenticateKeyPressed()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onAuthenticateKeyPressed_whenWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val password by collectLastValue(underTest.password)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPasswordInputChanged("wrong")
+
+ underTest.onAuthenticateKeyPressed()
+
+ assertThat(password).isEqualTo("")
+ assertThat(message).isEqualTo(WRONG_PASSWORD)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onAuthenticateKeyPressed_correctAfterWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val password by collectLastValue(underTest.password)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPasswordInputChanged("wrong")
+ underTest.onAuthenticateKeyPressed()
+ assertThat(password).isEqualTo("")
+ assertThat(message).isEqualTo(WRONG_PASSWORD)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ // Enter the correct password:
+ underTest.onPasswordInputChanged("password")
+ assertThat(message).isEmpty()
+
+ underTest.onAuthenticateKeyPressed()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ private const val ENTER_YOUR_PASSWORD = "Enter your password"
+ private const val WRONG_PASSWORD = "Wrong password"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
new file mode 100644
index 0000000..6ce29e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -0,0 +1,272 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+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.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PatternBouncerViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
+ )
+ private val sceneInteractor = utils.sceneInteractor()
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ private val bouncerViewModel =
+ utils.bouncerViewModel(
+ bouncerInteractor = bouncerInteractor,
+ )
+ private val underTest =
+ PatternBouncerViewModel(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ interactor = bouncerInteractor,
+ )
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN)
+ overrideResource(R.string.kg_wrong_pattern, WRONG_PATTERN)
+ }
+
+ @Test
+ fun onShown() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val selectedDots by collectLastValue(underTest.selectedDots)
+ val currentDot by collectLastValue(underTest.currentDot)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.onShown()
+
+ assertThat(message).isEqualTo(ENTER_YOUR_PATTERN)
+ assertThat(selectedDots).isEmpty()
+ assertThat(currentDot).isNull()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onDragStart() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val selectedDots by collectLastValue(underTest.selectedDots)
+ val currentDot by collectLastValue(underTest.currentDot)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+
+ underTest.onDragStart()
+
+ assertThat(message).isEmpty()
+ assertThat(selectedDots).isEmpty()
+ assertThat(currentDot).isNull()
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onDragEnd_whenCorrect() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val selectedDots by collectLastValue(underTest.selectedDots)
+ val currentDot by collectLastValue(underTest.currentDot)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onDragStart()
+ assertThat(currentDot).isNull()
+ CORRECT_PATTERN.forEachIndexed { index, coordinate ->
+ underTest.onDrag(
+ xPx = 30f * coordinate.x + 15,
+ yPx = 30f * coordinate.y + 15,
+ containerSizePx = 90,
+ verticalOffsetPx = 0f,
+ )
+ assertWithMessage("Wrong selected dots for index $index")
+ .that(selectedDots)
+ .isEqualTo(
+ CORRECT_PATTERN.subList(0, index + 1).map {
+ PatternDotViewModel(
+ x = it.x,
+ y = it.y,
+ )
+ }
+ )
+ assertWithMessage("Wrong current dot for index $index")
+ .that(currentDot)
+ .isEqualTo(
+ PatternDotViewModel(
+ x = CORRECT_PATTERN.subList(0, index + 1).last().x,
+ y = CORRECT_PATTERN.subList(0, index + 1).last().y,
+ )
+ )
+ }
+
+ underTest.onDragEnd()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onDragEnd_whenWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val selectedDots by collectLastValue(underTest.selectedDots)
+ val currentDot by collectLastValue(underTest.currentDot)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onDragStart()
+ CORRECT_PATTERN.subList(0, 3).forEach { coordinate ->
+ underTest.onDrag(
+ xPx = 30f * coordinate.x + 15,
+ yPx = 30f * coordinate.y + 15,
+ containerSizePx = 90,
+ verticalOffsetPx = 0f,
+ )
+ }
+
+ underTest.onDragEnd()
+
+ assertThat(selectedDots).isEmpty()
+ assertThat(currentDot).isNull()
+ assertThat(message).isEqualTo(WRONG_PATTERN)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onDragEnd_correctAfterWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val selectedDots by collectLastValue(underTest.selectedDots)
+ val currentDot by collectLastValue(underTest.currentDot)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern(CORRECT_PATTERN)
+ )
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onDragStart()
+ CORRECT_PATTERN.subList(2, 7).forEach { coordinate ->
+ underTest.onDrag(
+ xPx = 30f * coordinate.x + 15,
+ yPx = 30f * coordinate.y + 15,
+ containerSizePx = 90,
+ verticalOffsetPx = 0f,
+ )
+ }
+ underTest.onDragEnd()
+ assertThat(selectedDots).isEmpty()
+ assertThat(currentDot).isNull()
+ assertThat(message).isEqualTo(WRONG_PATTERN)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ // Enter the correct pattern:
+ CORRECT_PATTERN.forEach { coordinate ->
+ underTest.onDrag(
+ xPx = 30f * coordinate.x + 15,
+ yPx = 30f * coordinate.y + 15,
+ containerSizePx = 90,
+ verticalOffsetPx = 0f,
+ )
+ }
+
+ underTest.onDragEnd()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ private const val ENTER_YOUR_PATTERN = "Enter your pattern"
+ private const val WRONG_PATTERN = "Wrong pattern"
+ private val CORRECT_PATTERN =
+ listOf(
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 0),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 1),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 2, y = 2),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 1, y = 2),
+ AuthenticationMethodModel.Pattern.PatternCoordinate(x = 0, y = 2),
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
new file mode 100644
index 0000000..bb28520
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -0,0 +1,267 @@
+/*
+ * 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.bouncer.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PinBouncerViewModelTest : SysuiTestCase() {
+
+ private val testScope = TestScope()
+ private val utils = SceneTestUtils(this, testScope)
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
+ )
+ private val bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ )
+ private val bouncerViewModel =
+ BouncerViewModel(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ interactorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return bouncerInteractor
+ }
+ },
+ containerName = CONTAINER_NAME,
+ )
+ private val underTest =
+ PinBouncerViewModel(
+ applicationScope = testScope.backgroundScope,
+ interactor = bouncerInteractor,
+ )
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
+ overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
+ }
+
+ @Test
+ fun onShown() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ underTest.onShown()
+
+ assertThat(message).isEqualTo(ENTER_YOUR_PIN)
+ assertThat(pinLengths).isEqualTo(0 to 0)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onPinButtonClicked() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+
+ underTest.onPinButtonClicked(1)
+
+ assertThat(message).isEmpty()
+ assertThat(pinLengths).isEqualTo(0 to 1)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onBackspaceButtonClicked() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPinButtonClicked(1)
+ assertThat(pinLengths).isEqualTo(0 to 1)
+
+ underTest.onBackspaceButtonClicked()
+
+ assertThat(message).isEmpty()
+ assertThat(pinLengths).isEqualTo(1 to 0)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onBackspaceButtonLongPressed() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+
+ underTest.onBackspaceButtonLongPressed()
+ repeat(4) { index ->
+ assertThat(pinLengths).isEqualTo(4 - index to 3 - index)
+ advanceTimeBy(PinBouncerViewModel.BACKSPACE_LONG_PRESS_DELAY_MS)
+ }
+
+ assertThat(message).isEmpty()
+ assertThat(pinLengths).isEqualTo(1 to 0)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onAuthenticateButtonClicked_whenCorrect() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+
+ underTest.onAuthenticateButtonClicked()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onAuthenticateButtonClicked_whenWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+ underTest.onPinButtonClicked(5) // PIN is now wrong!
+
+ underTest.onAuthenticateButtonClicked()
+
+ assertThat(pinLengths).isEqualTo(0 to 0)
+ assertThat(message).isEqualTo(WRONG_PIN)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onAuthenticateButtonClicked_correctAfterWrong() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val message by collectLastValue(bouncerViewModel.message)
+ val pinLengths by collectLastValue(underTest.pinLengths)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Bouncer))
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ underTest.onShown()
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+ underTest.onPinButtonClicked(5) // PIN is now wrong!
+ underTest.onAuthenticateButtonClicked()
+ assertThat(message).isEqualTo(WRONG_PIN)
+ assertThat(pinLengths).isEqualTo(0 to 0)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+ // Enter the correct PIN:
+ underTest.onPinButtonClicked(1)
+ underTest.onPinButtonClicked(2)
+ underTest.onPinButtonClicked(3)
+ underTest.onPinButtonClicked(4)
+ assertThat(message).isEmpty()
+
+ underTest.onAuthenticateButtonClicked()
+
+ assertThat(isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ companion object {
+ private const val CONTAINER_NAME = "container1"
+ private const val ENTER_YOUR_PIN = "Enter your pin"
+ private const val WRONG_PIN = "Wrong pin"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
new file mode 100644
index 0000000..931f82c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -0,0 +1,190 @@
+package com.android.systemui.keyguard
+
+import android.content.ComponentCallbacks2
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.WakeSleepReason
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.utils.GlobalWindowManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+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.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ResourceTrimmerTest : SysuiTestCase() {
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val keyguardRepository = FakeKeyguardRepository()
+
+ @Mock private lateinit var globalWindowManager: GlobalWindowManager
+ @Mock private lateinit var featureFlags: FeatureFlags
+ private lateinit var resourceTrimmer: ResourceTrimmer
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(WakefulnessState.AWAKE, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
+ )
+ keyguardRepository.setDozeAmount(0f)
+
+ val interactor =
+ KeyguardInteractor(
+ keyguardRepository,
+ FakeCommandQueue(),
+ featureFlags,
+ FakeKeyguardBouncerRepository()
+ )
+ resourceTrimmer =
+ ResourceTrimmer(
+ interactor,
+ globalWindowManager,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+ resourceTrimmer.start()
+ }
+
+ @Test
+ fun noChange_noOutputChanges() =
+ testScope.runTest {
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ fun dozeAodDisabled_sleep_trimsMemory() =
+ testScope.runTest {
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+
+ @Test
+ fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(1f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+
+ @Test
+ fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0f) { it + 0.1f }
+ .takeWhile { it < 1f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+ verifyZeroInteractions(globalWindowManager)
+
+ keyguardRepository.setDozeAmount(1f)
+ testScope.runCurrent()
+ verify(globalWindowManager, times(1))
+ .trimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND)
+ }
+ }
+
+ @Test
+ fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(0f)
+ keyguardRepository.setWakefulnessModel(
+ WakefulnessModel(
+ WakefulnessState.ASLEEP,
+ WakeSleepReason.OTHER,
+ WakeSleepReason.OTHER
+ )
+ )
+
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0f) { it + 0.1f }
+ .takeWhile { it < 0.8f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+ verifyZeroInteractions(globalWindowManager)
+
+ generateSequence(0.8f) { it - 0.1f }
+ .takeWhile { it >= 0f }
+ .forEach {
+ keyguardRepository.setDozeAmount(it)
+ testScope.runCurrent()
+ }
+
+ keyguardRepository.setDozeAmount(0f)
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+ }
+}
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/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
index 8611359..29d7500 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -64,7 +64,6 @@
testScope = TestScope()
userRepository = FakeUserRepository()
userRepository.setUserInfos(users)
-
val logger =
TrustRepositoryLogger(
LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
@@ -224,4 +223,41 @@
assertThat(isCurrentUserTrusted()).isTrue()
}
+
+ @Test
+ fun isCurrentUserActiveUnlockRunning_runningFirstBeforeUserInfoChanges_emitsCorrectValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listener.capture())
+ val isCurrentUserActiveUnlockRunning by
+ collectLastValue(underTest.isCurrentUserActiveUnlockRunning)
+ userRepository.setSelectedUserInfo(users[1])
+
+ // active unlock running = true for users[0].id, but not the current user
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isFalse()
+
+ // current user is now users[0].id
+ userRepository.setSelectedUserInfo(users[0])
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserActiveUnlockRunning_whenActiveUnlockRunningForCurrentUser_emitsNewValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listener.capture())
+ val isCurrentUserActiveUnlockRunning by
+ collectLastValue(underTest.isCurrentUserActiveUnlockRunning)
+ userRepository.setSelectedUserInfo(users[0])
+
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+
+ listener.value.onIsActiveUnlockRunningChanged(false, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isFalse()
+
+ listener.value.onIsActiveUnlockRunningChanged(true, users[0].id)
+ assertThat(isCurrentUserActiveUnlockRunning).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index e261982..6af1220 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -35,7 +35,7 @@
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -100,8 +100,9 @@
mock(DismissCallbackRegistry::class.java),
context,
keyguardUpdateMonitor,
- mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeTrustRepository(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ testScope.backgroundScope,
),
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
index 749e7a0..c2c528a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
@@ -18,14 +18,10 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -43,34 +39,21 @@
class LockScreenSceneInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
+ private val utils = SceneTestUtils(this, testScope)
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
)
private val underTest =
- LockScreenSceneInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = mAuthenticationInteractor,
- bouncerInteractorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return BouncerInteractor(
- applicationScope = testScope.backgroundScope,
- applicationContext = context,
- repository = BouncerRepository(),
- authenticationInteractor = mAuthenticationInteractor,
- sceneInteractor = sceneInteractor,
- containerName = containerName,
- )
- }
- },
+ utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
)
@Test
@@ -78,10 +61,10 @@
testScope.runTest {
val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.lockDevice()
assertThat(isDeviceLocked).isTrue()
- mAuthenticationInteractor.unlockDevice()
+ authenticationInteractor.unlockDevice()
assertThat(isDeviceLocked).isFalse()
}
@@ -90,8 +73,8 @@
testScope.runTest {
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(isSwipeToDismissEnabled).isTrue()
}
@@ -101,8 +84,8 @@
testScope.runTest {
val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
- mAuthenticationInteractor.unlockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.unlockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(isSwipeToDismissEnabled).isFalse()
}
@@ -110,9 +93,9 @@
@Test
fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.dismissLockScreen()
@@ -123,9 +106,9 @@
@Test
fun dismissLockScreen_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.unlockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.unlockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.dismissLockScreen()
@@ -136,9 +119,9 @@
@Test
fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
underTest.dismissLockScreen()
@@ -149,15 +132,15 @@
@Test
fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
runCurrent()
- mAuthenticationInteractor.unlockDevice()
+ authenticationInteractor.unlockDevice()
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.lockDevice()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
}
@@ -165,15 +148,15 @@
@Test
fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- if (!mAuthenticationInteractor.isBypassEnabled.value) {
- mAuthenticationInteractor.toggleBypassEnabled()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+ if (!authenticationInteractor.isBypassEnabled.value) {
+ authenticationInteractor.toggleBypassEnabled()
}
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.biometricUnlock()
+ authenticationInteractor.biometricUnlock()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -181,15 +164,15 @@
@Test
fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- if (mAuthenticationInteractor.isBypassEnabled.value) {
- mAuthenticationInteractor.toggleBypassEnabled()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+ if (authenticationInteractor.isBypassEnabled.value) {
+ authenticationInteractor.toggleBypassEnabled()
}
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.biometricUnlock()
+ authenticationInteractor.biometricUnlock()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
}
@@ -197,12 +180,12 @@
@Test
fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
assertThat(isUnlocked).isTrue()
}
@@ -210,12 +193,12 @@
@Test
fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
assertThat(isUnlocked).isFalse()
}
@@ -223,15 +206,15 @@
@Test
fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
testScope.runTest {
- val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Shade))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
runCurrent()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
assertThat(isUnlocked).isFalse()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
assertThat(isUnlocked).isFalse()
}
@@ -239,12 +222,12 @@
@Test
fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.LockScreen))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
@@ -252,19 +235,15 @@
@Test
fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.QuickSettings))
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
runCurrent()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
}
-
- companion object {
- private const val CONTAINER_NAME = "container1"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
index eed1e739..5d39794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -44,6 +44,8 @@
import com.android.systemui.util.mockito.whenever
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -75,6 +77,7 @@
private lateinit var resources: TestableResources
private lateinit var trustRepository: FakeTrustRepository
private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var testScope: TestScope
@Before
fun setUp() {
@@ -83,9 +86,10 @@
.thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
DejankUtils.setImmediate(true)
+ testScope = TestScope()
mainHandler = FakeHandler(android.os.Looper.getMainLooper())
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
+ featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) }
underTest =
PrimaryBouncerInteractor(
repository,
@@ -100,6 +104,7 @@
keyguardUpdateMonitor,
trustRepository,
featureFlags,
+ testScope.backgroundScope,
)
whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -398,7 +403,6 @@
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
// GIVEN bouncer should be delayed due to face auth
- featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
.thenReturn(true)
@@ -420,26 +424,29 @@
@Test
fun delayBouncerWhenActiveUnlockPossible() {
- mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+ testScope.run {
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
- // GIVEN bouncer should be delayed due to active unlock
- featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
- trustRepository.setCurrentUserActiveUnlockAvailable(true)
- whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
+ // GIVEN bouncer should be delayed due to active unlock
+ trustRepository.setCurrentUserActiveUnlockAvailable(true)
+ whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState())
+ .thenReturn(true)
+ runCurrent()
- // WHEN bouncer show is requested
- underTest.show(true)
+ // WHEN bouncer show is requested
+ underTest.show(true)
- // THEN primary show & primary showing soon were scheduled to update
- verify(repository, never()).setPrimaryShow(true)
- verify(repository, never()).setPrimaryShowingSoon(false)
+ // THEN primary show & primary showing soon were scheduled to update
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
- // WHEN all queued messages are dispatched
- mainHandler.dispatchQueuedMessages()
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
- // THEN primary show & primary showing soon are updated
- verify(repository).setPrimaryShow(true)
- verify(repository).setPrimaryShowingSoon(false)
+ // THEN primary show & primary showing soon are updated
+ verify(repository).setPrimaryShow(true)
+ verify(repository).setPrimaryShowingSoon(false)
+ }
}
private fun updateSideFpsVisibilityParameters(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index 5056b43..b288fbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -34,6 +35,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -75,7 +77,8 @@
context,
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ TestScope().backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index e9f1ac1..15707c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -25,6 +25,7 @@
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.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -38,6 +39,7 @@
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -81,7 +83,8 @@
context,
keyguardUpdateMonitor,
Mockito.mock(TrustRepository::class.java),
- FakeFeatureFlags(),
+ FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, true) },
+ TestScope().backgroundScope,
)
underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
index d335b09..9e8be3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
@@ -19,16 +19,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -46,14 +42,11 @@
class LockScreenSceneViewModelTest : SysuiTestCase() {
private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
+ private val utils = SceneTestUtils(this, testScope)
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
)
private val underTest =
@@ -62,38 +55,28 @@
interactorFactory =
object : LockScreenSceneInteractor.Factory {
override fun create(containerName: String): LockScreenSceneInteractor {
- return LockScreenSceneInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = mAuthenticationInteractor,
- bouncerInteractorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return BouncerInteractor(
- applicationScope = testScope.backgroundScope,
- applicationContext = context,
- repository = BouncerRepository(),
- authenticationInteractor = mAuthenticationInteractor,
- sceneInteractor = sceneInteractor,
- containerName = containerName,
- )
- }
- },
+ return utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
)
}
},
- containerName = CONTAINER_NAME
+ containerName = CONTAINER_1
)
@Test
fun lockButtonIcon_whenLocked() =
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
- mAuthenticationInteractor.setAuthenticationMethod(
+ authenticationInteractor.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.lockDevice()
assertThat((lockButtonIcon as? Icon.Resource)?.res)
.isEqualTo(R.drawable.ic_device_lock_on)
@@ -103,10 +86,10 @@
fun lockButtonIcon_whenUnlocked() =
testScope.runTest {
val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
- mAuthenticationInteractor.setAuthenticationMethod(
+ authenticationInteractor.setAuthenticationMethod(
AuthenticationMethodModel.Password("password")
)
- mAuthenticationInteractor.unlockDevice()
+ authenticationInteractor.unlockDevice()
assertThat((lockButtonIcon as? Icon.Resource)?.res)
.isEqualTo(R.drawable.ic_device_lock_off)
@@ -116,8 +99,8 @@
fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.lockDevice()
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -126,8 +109,8 @@
fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
}
@@ -135,9 +118,9 @@
@Test
fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
runCurrent()
underTest.onLockButtonClicked()
@@ -148,9 +131,9 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
runCurrent()
underTest.onContentClicked()
@@ -161,9 +144,9 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
runCurrent()
underTest.onContentClicked()
@@ -174,17 +157,13 @@
@Test
fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
runCurrent()
underTest.onLockButtonClicked()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
}
-
- companion object {
- private const val CONTAINER_NAME = "container1"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index d428db7b..fd6e457 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -25,6 +25,7 @@
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget
import android.content.Intent
+import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Icon
import android.media.MediaDescription
@@ -40,6 +41,7 @@
import androidx.media.utils.MediaConstants
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IStatusBarService
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.R
@@ -76,6 +78,7 @@
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -130,6 +133,7 @@
@Mock lateinit var activityStarter: ActivityStarter
@Mock lateinit var smartspaceManager: SmartspaceManager
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock lateinit var statusBarService: IStatusBarService
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -192,7 +196,8 @@
mediaFlags = mediaFlags,
logger = logger,
smartspaceManager = smartspaceManager,
- keyguardUpdateMonitor = keyguardUpdateMonitor
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ statusBarService = statusBarService,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -517,19 +522,211 @@
}
@Test
- fun testOnNotificationRemoved_emptyTitle_notConverted() {
- // GIVEN that the manager has a notification with a resume action and empty title.
+ fun testOnNotificationAdded_emptyTitle_isRequired_notLoaded() {
+ // When the manager has a notification with an empty title, and the app is required
+ // to include a non-empty title
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
whenever(controller.metadata)
.thenReturn(
metadataBuilder
.putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
.build()
)
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is not added and we report a notification error
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
+ verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
+ }
+
+ @Test
+ fun testOnNotificationAdded_blankTitle_isRequired_notLoaded() {
+ // When the manager has a notification with a blank title, and the app is required
+ // to include a non-empty title
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is not added and we report a notification error
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
+ verify(logger, never()).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), any())
+ }
+
+ @Test
+ fun testOnNotificationUpdated_invalidTitle_isRequired_logMediaRemoved() {
+ // When the app is required to provide a non-blank title, and updates a previously valid
+ // title to an empty one
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(true)
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+
+ reset(listener)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then the media control is removed
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(statusBarService)
+ .onNotificationError(
+ eq(PACKAGE_NAME),
+ eq(mediaNotification.tag),
+ eq(mediaNotification.id),
+ eq(mediaNotification.uid),
+ eq(mediaNotification.initialPid),
+ eq(MEDIA_TITLE_ERROR_MESSAGE),
+ eq(mediaNotification.user.identifier)
+ )
+ verify(listener, never())
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId))
+ }
+
+ @Test
+ fun testOnNotificationAdded_emptyTitle_notRequired_hasPlaceholder() {
+ // When the manager has a notification with an empty title, and the app is not
+ // required to include a non-empty title
+ val mockPackageManager = mock(PackageManager::class.java)
+ context.setMockPackageManager(mockPackageManager)
+ whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_EMPTY_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then a media control is created with a placeholder title string
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun testOnNotificationAdded_blankTitle_notRequired_hasPlaceholder() {
+ // GIVEN that the manager has a notification with a blank title, and the app is not
+ // required to include a non-empty title
+ val mockPackageManager = mock(PackageManager::class.java)
+ context.setMockPackageManager(mockPackageManager)
+ whenever(mockPackageManager.getApplicationLabel(any())).thenReturn(APP_NAME)
+ whenever(mediaFlags.isMediaTitleRequired(any(), any())).thenReturn(false)
+ whenever(controller.metadata)
+ .thenReturn(
+ metadataBuilder
+ .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
+ .build()
+ )
+ mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+
+ // Then a media control is created with a placeholder title string
+ assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+ assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(KEY),
+ eq(null),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ val placeholderTitle = context.getString(R.string.controls_media_empty_title, APP_NAME)
+ assertThat(mediaDataCaptor.value.song).isEqualTo(placeholderTitle)
+ }
+
+ @Test
+ fun testOnNotificationRemoved_emptyTitle_notConverted() {
+ // GIVEN that the manager has a notification with a resume action and empty title.
addNotificationAndLoad()
val data = mediaDataCaptor.value
val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
- mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+ mediaDataManager.onMediaDataLoaded(
+ KEY,
+ null,
+ data.copy(song = SESSION_EMPTY_TITLE, resumeAction = Runnable {})
+ )
// WHEN the notification is removed
reset(listener)
@@ -554,17 +751,15 @@
@Test
fun testOnNotificationRemoved_blankTitle_notConverted() {
// GIVEN that the manager has a notification with a resume action and blank title.
- whenever(controller.metadata)
- .thenReturn(
- metadataBuilder
- .putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_BLANK_TITLE)
- .build()
- )
addNotificationAndLoad()
val data = mediaDataCaptor.value
val instanceId = data.instanceId
assertThat(data.resumption).isFalse()
- mediaDataManager.onMediaDataLoaded(KEY, null, data.copy(resumeAction = Runnable {}))
+ mediaDataManager.onMediaDataLoaded(
+ KEY,
+ null,
+ data.copy(song = SESSION_BLANK_TITLE, resumeAction = Runnable {})
+ )
// WHEN the notification is removed
reset(listener)
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/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 480d59c..f79c53d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -54,6 +54,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.Before;
@@ -91,6 +92,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -123,7 +125,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 891a6f8..705b485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -50,6 +50,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
@@ -95,6 +96,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
private MediaOutputController mMediaOutputController;
@@ -109,7 +111,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
mBroadcastSender, mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 299303d..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
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -48,13 +49,18 @@
import android.media.MediaRoute2Info;
import android.media.NearbyDevice;
import android.media.RoutingSessionInfo;
+import android.media.session.ISessionController;
import android.media.session.MediaController;
+import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.text.TextUtils;
import android.view.View;
@@ -70,9 +76,9 @@
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;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -91,6 +97,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaOutputControllerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "com.test.package.name";
@@ -111,7 +118,7 @@
private NearbyMediaDevicesManager mNearbyMediaDevicesManager;
// Mock
@Mock
- private MediaController mMediaController;
+ private MediaController mSessionMediaController;
@Mock
private MediaSessionManager mMediaSessionManager;
@Mock
@@ -151,10 +158,15 @@
@Mock
private PlaybackState mPlaybackState;
+ @Mock
+ private UserTracker mUserTracker;
+
private FeatureFlags mFlags = mock(FeatureFlags.class);
private View mDialogLaunchView = mock(View.class);
private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
+ final Notification mNotification = mock(Notification.class);
+
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
private LocalMediaManager mLocalMediaManager;
@@ -169,10 +181,14 @@
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPackageManager);
mSpyContext = spy(mContext);
- when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
- mMediaControllers.add(mMediaController);
- when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+ final UserHandle userHandle = mock(UserHandle.class);
+ when(mUserTracker.getUserHandle()).thenReturn(userHandle);
+ when(mSessionMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(mSessionMediaController.getPlaybackState()).thenReturn(mPlaybackState);
+ mMediaControllers.add(mSessionMediaController);
+ when(mMediaSessionManager.getActiveSessionsForUser(any(),
+ Mockito.eq(userHandle))).thenReturn(
+ mMediaControllers);
doReturn(mMediaSessionManager).when(mSpyContext).getSystemService(
MediaSessionManager.class);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
@@ -182,9 +198,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT)).thenReturn(false);
- when(mFlags.isEnabled(Flags.OUTPUT_SWITCHER_ROUTES_PROCESSING)).thenReturn(false);
+ mKeyguardManager, mFlags, mUserTracker);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
@@ -205,6 +219,24 @@
when(mNearbyDevice2.getRangeZone()).thenReturn(NearbyDevice.RANGE_CLOSE);
mNearbyDevices.add(mNearbyDevice1);
mNearbyDevices.add(mNearbyDevice2);
+
+ final List<NotificationEntry> entryList = new ArrayList<>();
+ final NotificationEntry entry = mock(NotificationEntry.class);
+ final StatusBarNotification sbn = mock(StatusBarNotification.class);
+ final Bundle bundle = mock(Bundle.class);
+ final MediaSession.Token token = mock(MediaSession.Token.class);
+ final ISessionController binder = mock(ISessionController.class);
+ entryList.add(entry);
+
+ when(mNotification.isMediaNotification()).thenReturn(false);
+ when(mNotifCollection.getAllNotifs()).thenReturn(entryList);
+ when(entry.getSbn()).thenReturn(sbn);
+ when(sbn.getNotification()).thenReturn(mNotification);
+ when(sbn.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ mNotification.extras = bundle;
+ when(bundle.getParcelable(Notification.EXTRA_MEDIA_SESSION,
+ MediaSession.Token.class)).thenReturn(token);
+ when(token.getBinder()).thenReturn(binder);
}
@Test
@@ -227,10 +259,19 @@
}
@Test
- public void start_withPackageName_verifyMediaControllerInit() {
+ public void start_notificationNotFound_mediaControllerInitFromSession() {
mMediaOutputController.start(mCb);
- verify(mMediaController).registerCallback(any());
+ verify(mSessionMediaController).registerCallback(any());
+ }
+
+ @Test
+ public void start_MediaNotificationFound_mediaControllerNotInitFromSession() {
+ when(mNotification.isMediaNotification()).thenReturn(true);
+ mMediaOutputController.start(mCb);
+
+ verify(mSessionMediaController, never()).registerCallback(any());
+ verifyZeroInteractions(mMediaSessionManager);
}
@Test
@@ -239,11 +280,11 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.start(mCb);
- verify(mMediaController, never()).registerCallback(any());
+ verify(mSessionMediaController, never()).registerCallback(any());
}
@Test
@@ -256,11 +297,11 @@
@Test
public void stop_withPackageName_verifyMediaControllerDeinit() {
mMediaOutputController.start(mCb);
- reset(mMediaController);
+ reset(mSessionMediaController);
mMediaOutputController.stop();
- verify(mMediaController).unregisterCallback(any());
+ verify(mSessionMediaController).unregisterCallback(any());
}
@Test
@@ -269,19 +310,19 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.start(mCb);
mMediaOutputController.stop();
- verify(mMediaController, never()).unregisterCallback(any());
+ verify(mSessionMediaController, never()).unregisterCallback(any());
}
@Test
public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
mMediaOutputController.start(mCb);
- reset(mMediaController);
+ reset(mSessionMediaController);
mMediaOutputController.stop();
@@ -358,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);
@@ -387,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);
@@ -409,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);
@@ -433,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);
@@ -471,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;
@@ -509,7 +531,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -532,7 +554,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.start(mCb);
reset(mCb);
@@ -568,7 +590,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -585,7 +607,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -673,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);
@@ -684,7 +705,7 @@
@Test
public void isPlaying_stateIsNull() {
- when(mMediaController.getPlaybackState()).thenReturn(null);
+ when(mSessionMediaController.getPlaybackState()).thenReturn(null);
assertThat(mMediaOutputController.isPlaying()).isFalse();
}
@@ -726,7 +747,7 @@
@Test
public void getHeaderTitle_withoutMetadata_returnDefaultString() {
- when(mMediaController.getMetadata()).thenReturn(null);
+ when(mSessionMediaController.getMetadata()).thenReturn(null);
mMediaOutputController.start(mCb);
@@ -736,7 +757,7 @@
@Test
public void getHeaderTitle_withMetadata_returnSongName() {
- when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+ when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
mMediaOutputController.start(mCb);
@@ -745,7 +766,7 @@
@Test
public void getHeaderSubTitle_withoutMetadata_returnNull() {
- when(mMediaController.getMetadata()).thenReturn(null);
+ when(mSessionMediaController.getMetadata()).thenReturn(null);
mMediaOutputController.start(mCb);
@@ -754,7 +775,7 @@
@Test
public void getHeaderSubTitle_withMetadata_returnArtistName() {
- when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
+ when(mSessionMediaController.getMetadata()).thenReturn(mMediaMetadata);
mMediaOutputController.start(mCb);
@@ -868,7 +889,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -1060,7 +1081,7 @@
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
testMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 425d0bc..f3aee48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -34,6 +34,7 @@
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.PowerExemptionManager;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.FeatureFlagUtils;
@@ -55,12 +56,14 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -98,6 +101,7 @@
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private FeatureFlags mFlags = mock(FeatureFlags.class);
+ private UserTracker mUserTracker = mock(UserTracker.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -119,13 +123,17 @@
when(mMediaController.getMetadata()).thenReturn(mMediaMetadata);
when(mMediaMetadata.getDescription()).thenReturn(mMediaDescription);
mMediaControllers.add(mMediaController);
- when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);
+ final UserHandle userHandle = mock(UserHandle.class);
+ when(mUserTracker.getUserHandle()).thenReturn(userHandle);
+ when(mMediaSessionManager.getActiveSessionsForUser(any(),
+ Mockito.eq(userHandle))).thenReturn(
+ mMediaControllers);
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
- mKeyguardManager, mFlags);
+ mKeyguardManager, mFlags, mUserTracker);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = makeTestDialog(mMediaOutputController);
mMediaOutputDialog.show();
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/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 84cc977..6614392 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -43,6 +43,7 @@
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
import android.telephony.TelephonyDisplayInfo;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -961,6 +962,26 @@
assertThat(dds).contains(mContext.getString(R.string.carrier_network_change_mode));
}
+ @Test
+ public void onStop_cleanUp() {
+ doReturn(SUB_ID).when(mTelephonyManager).getSubscriptionId();
+ assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.get(SUB_ID)).isEqualTo(
+ mTelephonyManager);
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.get(SUB_ID)).isNotNull();
+
+ mInternetDialogController.onStop();
+
+ verify(mTelephonyManager).unregisterTelephonyCallback(any(TelephonyCallback.class));
+ assertThat(mInternetDialogController.mSubIdTelephonyDisplayInfoMap.isEmpty()).isTrue();
+ assertThat(mInternetDialogController.mSubIdTelephonyManagerMap.isEmpty()).isTrue();
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.isEmpty()).isTrue();
+ verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(mInternetDialogController
+ .mOnSubscriptionsChangedListener);
+ verify(mAccessPointController).removeAccessPointCallback(mInternetDialogController);
+ verify(mConnectivityManager).unregisterNetworkCallback(
+ any(ConnectivityManager.NetworkCallback.class));
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index e8875be..3f838e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -18,15 +18,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -44,14 +40,11 @@
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
+ private val utils = SceneTestUtils(this, testScope)
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
)
private val underTest =
@@ -59,36 +52,26 @@
lockScreenSceneInteractorFactory =
object : LockScreenSceneInteractor.Factory {
override fun create(containerName: String): LockScreenSceneInteractor {
- return LockScreenSceneInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = mAuthenticationInteractor,
- bouncerInteractorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return BouncerInteractor(
- applicationScope = testScope.backgroundScope,
- applicationContext = context,
- repository = BouncerRepository(),
- authenticationInteractor = mAuthenticationInteractor,
- sceneInteractor = sceneInteractor,
- containerName = containerName,
- )
- }
- },
+ return utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
)
}
},
- containerName = CONTAINER_NAME
+ containerName = CONTAINER_1
)
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
runCurrent()
underTest.onContentClicked()
@@ -99,17 +82,13 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
runCurrent()
underTest.onContentClicked()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
-
- companion object {
- private const val CONTAINER_NAME = "container1"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt
deleted file mode 100644
index 1cdaec0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.data.repository
-
-import com.android.systemui.scene.data.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
-
-fun fakeSceneContainerRepository(
- containerConfigurations: Set<SceneContainerConfig> =
- setOf(
- fakeSceneContainerConfig("container1"),
- fakeSceneContainerConfig("container2"),
- )
-): SceneContainerRepository {
- return SceneContainerRepository(containerConfigurations)
-}
-
-fun fakeSceneKeys(): List<SceneKey> {
- return listOf(
- SceneKey.QuickSettings,
- SceneKey.Shade,
- SceneKey.LockScreen,
- SceneKey.Bouncer,
- SceneKey.Gone,
- )
-}
-
-fun fakeSceneContainerConfig(
- name: String,
- sceneKeys: List<SceneKey> = fakeSceneKeys(),
-): SceneContainerConfig {
- return SceneContainerConfig(
- name = name,
- sceneKeys = sceneKeys,
- initialSceneKey = SceneKey.LockScreen,
- )
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 9e264db8..6c7017bac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -21,6 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -34,9 +35,11 @@
@RunWith(JUnit4::class)
class SceneContainerRepositoryTest : SysuiTestCase() {
+ private val utils = SceneTestUtils(this)
+
@Test
fun allSceneKeys() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
assertThat(underTest.allSceneKeys("container1"))
.isEqualTo(
listOf(
@@ -51,13 +54,13 @@
@Test(expected = IllegalStateException::class)
fun allSceneKeys_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.allSceneKeys("nonExistingContainer")
}
@Test
fun currentScene() = runTest {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
val currentScene by collectLastValue(underTest.currentScene("container1"))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
@@ -67,23 +70,23 @@
@Test(expected = IllegalStateException::class)
fun currentScene_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.currentScene("nonExistingContainer")
}
@Test(expected = IllegalStateException::class)
fun setCurrentScene_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade))
}
@Test(expected = IllegalStateException::class)
fun setCurrentScene_noSuchSceneInContainer_throws() {
val underTest =
- fakeSceneContainerRepository(
+ utils.fakeSceneContainerRepository(
setOf(
- fakeSceneContainerConfig("container1"),
- fakeSceneContainerConfig(
+ utils.fakeSceneContainerConfig("container1"),
+ utils.fakeSceneContainerConfig(
"container2",
listOf(SceneKey.QuickSettings, SceneKey.LockScreen)
),
@@ -94,7 +97,7 @@
@Test
fun isVisible() = runTest {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
val isVisible by collectLastValue(underTest.isVisible("container1"))
assertThat(isVisible).isTrue()
@@ -107,19 +110,19 @@
@Test(expected = IllegalStateException::class)
fun isVisible_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.isVisible("nonExistingContainer")
}
@Test(expected = IllegalStateException::class)
fun setVisible_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.setVisible("nonExistingContainer", false)
}
@Test
fun sceneTransitionProgress() = runTest {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
val sceneTransitionProgress by
collectLastValue(underTest.sceneTransitionProgress("container1"))
assertThat(sceneTransitionProgress).isEqualTo(1f)
@@ -133,7 +136,7 @@
@Test(expected = IllegalStateException::class)
fun sceneTransitionProgress_noSuchContainer_throws() {
- val underTest = fakeSceneContainerRepository()
+ val underTest = utils.fakeSceneContainerRepository()
underTest.sceneTransitionProgress("nonExistingContainer")
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index c5ce092..cf99e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -21,8 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.data.repository.fakeSceneKeys
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -36,14 +35,12 @@
@RunWith(JUnit4::class)
class SceneInteractorTest : SysuiTestCase() {
- private val underTest =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
+ private val utils = SceneTestUtils(this)
+ private val underTest = utils.sceneInteractor()
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys("container1")).isEqualTo(fakeSceneKeys())
+ assertThat(underTest.allSceneKeys("container1")).isEqualTo(utils.fakeSceneKeys())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ab61ddd..6105c87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,9 +21,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.data.repository.fakeSceneKeys
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -36,10 +34,9 @@
@SmallTest
@RunWith(JUnit4::class)
class SceneContainerViewModelTest : SysuiTestCase() {
- private val interactor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
+
+ private val utils = SceneTestUtils(this)
+ private val interactor = utils.sceneInteractor()
private val underTest =
SceneContainerViewModel(
interactor = interactor,
@@ -60,7 +57,7 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys).isEqualTo(fakeSceneKeys())
+ assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 688cce8..2e7f83d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -18,15 +18,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.data.repo.BouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
-import com.android.systemui.scene.data.repository.fakeSceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.CONTAINER_1
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.google.common.truth.Truth.assertThat
@@ -44,14 +40,11 @@
class ShadeSceneViewModelTest : SysuiTestCase() {
private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
+ private val utils = SceneTestUtils(this, testScope)
+ private val sceneInteractor = utils.sceneInteractor()
+ private val authenticationInteractor =
+ utils.authenticationInteractor(
+ repository = utils.authenticationRepository(),
)
private val underTest =
@@ -60,36 +53,26 @@
lockScreenSceneInteractorFactory =
object : LockScreenSceneInteractor.Factory {
override fun create(containerName: String): LockScreenSceneInteractor {
- return LockScreenSceneInteractor(
- applicationScope = testScope.backgroundScope,
- authenticationInteractor = mAuthenticationInteractor,
- bouncerInteractorFactory =
- object : BouncerInteractor.Factory {
- override fun create(containerName: String): BouncerInteractor {
- return BouncerInteractor(
- applicationScope = testScope.backgroundScope,
- applicationContext = context,
- repository = BouncerRepository(),
- authenticationInteractor = mAuthenticationInteractor,
- sceneInteractor = sceneInteractor,
- containerName = containerName,
- )
- }
- },
+ return utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
)
}
},
- containerName = CONTAINER_NAME
+ containerName = SceneTestUtils.CONTAINER_1
)
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.LockScreen)
}
@@ -98,8 +81,8 @@
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -107,9 +90,9 @@
@Test
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
runCurrent()
underTest.onContentClicked()
@@ -120,17 +103,13 @@
@Test
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
runCurrent()
underTest.onContentClicked()
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
}
-
- companion object {
- private const val CONTAINER_NAME = "container1"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index f7fcab1..542e0cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -1066,11 +1066,11 @@
}
@Test
- public void onRefreshBatteryInfo_chargingWithOverheat_presentChargingLimited() {
+ public void onRefreshBatteryInfo_chargingWithLongLife_presentChargingLimited() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
80 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+ BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
@@ -1084,11 +1084,11 @@
}
@Test
- public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
+ public void onRefreshBatteryInfo_fullChargedWithLongLife_presentChargingLimited() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_OVERHEAT, 0 /* maxChargingWattage */,
+ BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE, 0 /* maxChargingWattage */,
true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
@@ -1102,11 +1102,11 @@
}
@Test
- public void onRefreshBatteryInfo_fullChargedWithoutOverheat_presentCharged() {
+ public void onRefreshBatteryInfo_fullChargedWithoutLongLife_presentCharged() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
- BatteryManager.BATTERY_HEALTH_GOOD, 0 /* maxChargingWattage */,
+ BatteryManager.CHARGING_POLICY_DEFAULT, 0 /* maxChargingWattage */,
true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
@@ -1118,11 +1118,11 @@
}
@Test
- public void onRefreshBatteryInfo_dozing_dischargingWithOverheat_presentBatteryPercentage() {
+ public void onRefreshBatteryInfo_dozing_dischargingWithLongLife_presentBatteryPercentage() {
createController();
mController.setVisible(true);
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_DISCHARGING,
- 90 /* level */, 0 /* plugged */, BatteryManager.BATTERY_HEALTH_OVERHEAT,
+ 90 /* level */, 0 /* plugged */, BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE,
0 /* maxChargingWattage */, true /* present */);
mController.getKeyguardCallback().onRefreshBatteryInfo(status);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 91c88ce..c886f9b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -226,31 +226,33 @@
}
@Test
- public void batteryStateChanged_healthNotOverheated_outputsFalse() {
+ public void batteryStateChanged_chargingStatusNotLongLife_outputsFalse() {
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_GOOD);
+ intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS,
+ BatteryManager.CHARGING_POLICY_DEFAULT);
mBatteryController.onReceive(getContext(), intent);
- Assert.assertFalse(mBatteryController.isOverheated());
+ Assert.assertFalse(mBatteryController.isBatteryDefender());
}
@Test
- public void batteryStateChanged_healthOverheated_outputsTrue() {
+ public void batteryStateChanged_chargingStatusLongLife_outputsTrue() {
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_OVERHEAT);
+ intent.putExtra(BatteryManager.EXTRA_CHARGING_STATUS,
+ BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE);
mBatteryController.onReceive(getContext(), intent);
- Assert.assertTrue(mBatteryController.isOverheated());
+ Assert.assertTrue(mBatteryController.isBatteryDefender());
}
@Test
- public void batteryStateChanged_noHealthGiven_outputsFalse() {
+ public void batteryStateChanged_noChargingStatusGiven_outputsFalse() {
Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
mBatteryController.onReceive(getContext(), intent);
- Assert.assertFalse(mBatteryController.isOverheated());
+ Assert.assertFalse(mBatteryController.isBatteryDefender());
}
}
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/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 47a86b1..f0683a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -300,6 +300,10 @@
private UserHandle mUser0;
+ // The window context being used by the controller, use this to verify
+ // any actions on the context.
+ private Context mBubbleControllerContext;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -436,6 +440,8 @@
// Get a reference to KeyguardStateController.Callback
verify(mKeyguardStateController, atLeastOnce())
.addCallback(mKeyguardStateControllerCallbackCaptor.capture());
+
+ mBubbleControllerContext = mBubbleController.getContext();
}
@After
@@ -468,11 +474,6 @@
}
@Test
- public void instantiateController_registerConfigChangeListener() {
- verify(mShellController, times(1)).addConfigurationChangeListener(any());
- }
-
- @Test
public void testAddBubble() {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
@@ -1385,13 +1386,28 @@
assertStackCollapsed();
}
+ @Test
+ public void testRegisterUnregisterComponentCallbacks() {
+ spyOn(mBubbleControllerContext);
+ mBubbleController.updateBubble(mBubbleEntry);
+ verify(mBubbleControllerContext).registerComponentCallbacks(eq(mBubbleController));
+
+ mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
+ // TODO: not certain why this isn't called normally when tests are run, perhaps because
+ // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
+ mBubbleController.onAllBubblesAnimatedOut();
+
+ verify(mBubbleControllerContext).unregisterComponentCallbacks(eq(mBubbleController));
+ }
@Test
public void testRegisterUnregisterBroadcastListener() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
assertThat(mFilterArgumentCaptor.getValue()
.hasAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)).isTrue();
assertThat(mFilterArgumentCaptor.getValue()
@@ -1402,47 +1418,54 @@
// it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
mBubbleController.onAllBubblesAnimatedOut();
- verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
+ verify(mBubbleControllerContext).unregisterReceiver(
+ eq(mBroadcastReceiverArgumentCaptor.getValue()));
}
@Test
public void testBroadcastReceiverCloseDialogs_notGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackExpanded();
}
@Test
public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
i.putExtra("reason", "gestureNav");
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
@Test
public void testBroadcastReceiver_screenOff() {
- spyOn(mContext);
+ spyOn(mBubbleControllerContext);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED));
+ verify(mBubbleControllerContext).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(),
+ mFilterArgumentCaptor.capture(),
+ eq(Context.RECEIVER_EXPORTED));
Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mBubbleControllerContext, i);
assertStackCollapsed();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 5ff57aa..4b6dd3e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.testing.LeakCheck;
@@ -62,6 +63,10 @@
return (SysuiTestableContext) createDisplayContext(display);
}
+ public SysuiTestableContext createWindowContext(int type, Bundle bundle) {
+ return new SysuiTestableContext(getBaseContext().createWindowContext(type, bundle));
+ }
+
public void cleanUpReceivers(String testName) {
Set<BroadcastReceiver> copy;
synchronized (mRegisteredReceivers) {
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/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index fd8c4b8..b52a768 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -147,6 +147,10 @@
_isAodAvailable.value = isAodAvailable
}
+ fun setDreaming(isDreaming: Boolean) {
+ _isDreaming.value = isDreaming
+ }
+
fun setDreamingWithOverlay(isDreaming: Boolean) {
_isDreamingWithOverlay.value = isDreaming
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 1340a47..817e1db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -28,7 +28,7 @@
get() = _isCurrentUserTrusted
private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
- override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
+ override val isCurrentUserActiveUnlockRunning: StateFlow<Boolean> =
_isCurrentUserActiveUnlockAvailable.asStateFlow()
private val _isCurrentUserTrustManaged = MutableStateFlow(false)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
new file mode 100644
index 0000000..5a350bb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -0,0 +1,161 @@
+/*
+ * 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.scene
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.AuthenticationRepository
+import com.android.systemui.authentication.data.repository.AuthenticationRepositoryImpl
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.data.repo.BouncerRepository
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.scene.data.model.SceneContainerConfig
+import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+
+/**
+ * Utilities for creating scene container framework related repositories, interactors, and
+ * view-models for tests.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SceneTestUtils(
+ test: SysuiTestCase,
+ private val testScope: TestScope? = null,
+) {
+
+ private val context = test.context
+
+ fun fakeSceneContainerRepository(
+ containerConfigurations: Set<SceneContainerConfig> =
+ setOf(
+ fakeSceneContainerConfig(CONTAINER_1),
+ fakeSceneContainerConfig(CONTAINER_2),
+ )
+ ): SceneContainerRepository {
+ return SceneContainerRepository(containerConfigurations)
+ }
+
+ fun fakeSceneKeys(): List<SceneKey> {
+ return listOf(
+ SceneKey.QuickSettings,
+ SceneKey.Shade,
+ SceneKey.LockScreen,
+ SceneKey.Bouncer,
+ SceneKey.Gone,
+ )
+ }
+
+ fun fakeSceneContainerConfig(
+ name: String,
+ sceneKeys: List<SceneKey> = fakeSceneKeys(),
+ ): SceneContainerConfig {
+ return SceneContainerConfig(
+ name = name,
+ sceneKeys = sceneKeys,
+ initialSceneKey = SceneKey.LockScreen,
+ )
+ }
+
+ fun sceneInteractor(): SceneInteractor {
+ return SceneInteractor(
+ repository = fakeSceneContainerRepository(),
+ )
+ }
+
+ fun authenticationRepository(): AuthenticationRepository {
+ return AuthenticationRepositoryImpl()
+ }
+
+ fun authenticationInteractor(
+ repository: AuthenticationRepository,
+ ): AuthenticationInteractor {
+ return AuthenticationInteractor(
+ applicationScope = applicationScope(),
+ repository = repository,
+ )
+ }
+
+ private fun applicationScope(): CoroutineScope {
+ return checkNotNull(testScope) {
+ """
+ TestScope not initialized, please create a TestScope and inject it into
+ SceneTestUtils.
+ """
+ .trimIndent()
+ }
+ .backgroundScope
+ }
+
+ fun bouncerInteractor(
+ authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+ ): BouncerInteractor {
+ return BouncerInteractor(
+ applicationScope = applicationScope(),
+ applicationContext = context,
+ repository = BouncerRepository(),
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_1,
+ )
+ }
+
+ fun bouncerViewModel(
+ bouncerInteractor: BouncerInteractor,
+ ): BouncerViewModel {
+ return BouncerViewModel(
+ applicationContext = context,
+ applicationScope = applicationScope(),
+ interactorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return bouncerInteractor
+ }
+ },
+ containerName = CONTAINER_1,
+ )
+ }
+
+ fun lockScreenSceneInteractor(
+ authenticationInteractor: AuthenticationInteractor,
+ sceneInteractor: SceneInteractor,
+ bouncerInteractor: BouncerInteractor,
+ ): LockScreenSceneInteractor {
+ return LockScreenSceneInteractor(
+ applicationScope = applicationScope(),
+ authenticationInteractor = authenticationInteractor,
+ bouncerInteractorFactory =
+ object : BouncerInteractor.Factory {
+ override fun create(containerName: String): BouncerInteractor {
+ return bouncerInteractor
+ }
+ },
+ sceneInteractor = sceneInteractor,
+ containerName = CONTAINER_1,
+ )
+ }
+
+ companion object {
+ const val CONTAINER_1 = "container1"
+ const val CONTAINER_2 = "container2"
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
index 93531dd..92fd419 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/AlwaysOnMagnificationFeatureFlag.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility.magnification;
+import android.annotation.NonNull;
+import android.content.Context;
import android.provider.DeviceConfig;
/**
@@ -29,6 +31,13 @@
private static final String FEATURE_NAME_ENABLE_ALWAYS_ON_MAGNIFICATION =
"AlwaysOnMagnifier__enable_always_on_magnifier";
+ private @NonNull Context mContext;
+
+ AlwaysOnMagnificationFeatureFlag(@NonNull Context context) {
+ super();
+ mContext = context;
+ }
+
@Override
String getNamespace() {
return NAMESPACE;
@@ -41,6 +50,7 @@
@Override
boolean getDefaultValue() {
- return false;
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_magnification_always_on_enabled);
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 9b1c204..87fbee7 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -156,7 +156,7 @@
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
- mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag();
+ mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
mBackgroundExecutor, mAms::updateAlwaysOnMagnification);
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index af5b196..fc758cb 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -761,6 +761,12 @@
}
// Called by Shell command
+ String getFieldDetectionServiceName(@UserIdInt int userId) {
+ enforceCallingPermissionForManagement();
+ return mFieldClassificationResolver.readServiceName(userId);
+ }
+
+ // Called by Shell command
boolean setTemporaryDetectionService(@UserIdInt int userId, @NonNull String serviceName,
int durationMs) {
Slog.i(mTag, "setTemporaryDetectionService(" + userId + ") to " + serviceName
@@ -903,9 +909,9 @@
}
/**
- * Whether the Autofill PCC Classification feature is enabled.
+ * Whether the Autofill PCC Classification feature flag is enabled.
*/
- public boolean isPccClassificationEnabled() {
+ public boolean isPccClassificationFlagEnabled() {
synchronized (mFlagLock) {
return mPccClassificationEnabled;
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index d5dcdaf..63a607c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1730,6 +1730,23 @@
return mRemoteFieldClassificationService;
}
+
+ public boolean isPccClassificationEnabled() {
+ boolean result = isPccClassificationEnabledInternal();
+ if (sVerbose) {
+ Slog.v(TAG, "pccEnabled: " + result);
+ }
+ return result;
+ }
+
+ public boolean isPccClassificationEnabledInternal() {
+ boolean flagEnabled = mMaster.isPccClassificationFlagEnabled();
+ if (!flagEnabled) return false;
+ synchronized (mLock) {
+ return getRemoteFieldClassificationServiceLocked() != null;
+ }
+ }
+
/**
* Called when the {@link AutofillManagerService#mFieldClassificationResolver}
* changed (among other places).
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 62a2970..4aeb4a4 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -26,6 +26,7 @@
import android.os.ShellCommand;
import android.os.UserHandle;
import android.service.autofill.AutofillFieldClassificationService.Scores;
+import android.text.TextUtils;
import android.view.autofill.AutofillManager;
import com.android.internal.os.IResultReceiver;
@@ -154,6 +155,8 @@
return getBindInstantService(pw);
case "default-augmented-service-enabled":
return getDefaultAugmentedServiceEnabled(pw);
+ case "field-detection-service-enabled":
+ return isFieldDetectionServiceEnabled(pw);
case "saved-password-count":
return getSavedPasswordCount(pw);
default:
@@ -343,6 +346,14 @@
return 0;
}
+ private int isFieldDetectionServiceEnabled(PrintWriter pw) {
+ final int userId = getNextIntArgRequired();
+ String name = mService.getFieldDetectionServiceName(userId);
+ boolean enabled = !TextUtils.isEmpty(name);
+ pw.println(enabled);
+ return 0;
+ }
+
private int setTemporaryAugmentedService(PrintWriter pw) {
final int userId = getNextIntArgRequired();
final String serviceName = getNextArg();
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 c4c1750..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;
@@ -798,7 +805,7 @@
* Returns empty list if PCC is off or no types available
*/
private List<String> getTypeHintsForProvider() {
- if (!mService.getMaster().isPccClassificationEnabled()) {
+ if (!mService.isPccClassificationEnabled()) {
return Collections.EMPTY_LIST;
}
final String typeHints = mService.getMaster().getPccProviderHints();
@@ -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;
@@ -1200,7 +1207,7 @@
// structure is taken. This causes only one fill request per burst of focus changes.
cancelCurrentRequestLocked();
- if (mService.getMaster().isPccClassificationEnabled()
+ if (mService.isPccClassificationEnabled()
&& mClassificationState.mHintsToAutofillIdMap == null) {
if (sVerbose) {
Slog.v(TAG, "triggering field classification");
@@ -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.
@@ -1631,7 +1640,7 @@
Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: "
+ autofillProviderContainer);
}
- if (!mService.getMaster().isPccClassificationEnabled()) {
+ if (!mService.isPccClassificationEnabled()) {
if (sVerbose) {
Slog.v(TAG, "PCC classification is disabled");
}
@@ -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/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index dec0e76..dbeb624 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -29,6 +29,7 @@
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.view.ContextThemeWrapper;
@@ -177,7 +178,14 @@
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
final WindowManager.LayoutParams params = window.getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ window.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ final int screenWidth = displayMetrics.widthPixels;
+ final int maxWidth =
+ mContext.getResources().getDimensionPixelSize(R.dimen.autofill_dialog_max_width);
+ params.width = Math.min(screenWidth, maxWidth);
+
params.accessibilityTitle =
mContext.getString(R.string.autofill_picker_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 1204259..f035d07 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -48,6 +48,7 @@
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -361,7 +362,14 @@
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
final WindowManager.LayoutParams params = window.getAttributes();
- params.width = WindowManager.LayoutParams.MATCH_PARENT;
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ window.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ final int screenWidth = displayMetrics.widthPixels;
+ final int maxWidth =
+ context.getResources().getDimensionPixelSize(R.dimen.autofill_dialog_max_width);
+ params.width = Math.min(screenWidth, maxWidth);
+
params.accessibilityTitle = context.getString(R.string.autofill_save_accessibility_title);
params.windowAnimations = R.style.AutofillSaveAnimation;
params.setTrustedOverlay();
@@ -563,25 +571,7 @@
private void setServiceIcon(Context context, View view, Drawable serviceIcon) {
final ImageView iconView = view.findViewById(R.id.autofill_save_icon);
final Resources res = context.getResources();
-
- final int maxWidth = res.getDimensionPixelSize(R.dimen.autofill_save_icon_max_size);
- final int maxHeight = maxWidth;
- final int actualWidth = serviceIcon.getMinimumWidth();
- final int actualHeight = serviceIcon.getMinimumHeight();
-
- if (actualWidth <= maxWidth && actualHeight <= maxHeight) {
- if (sDebug) {
- Slog.d(TAG, "Adding service icon "
- + "(" + actualWidth + "x" + actualHeight + ") as it's less than maximum "
- + "(" + maxWidth + "x" + maxHeight + ").");
- }
- iconView.setImageDrawable(serviceIcon);
- } else {
- Slog.w(TAG, "Not adding service icon of size "
- + "(" + actualWidth + "x" + actualHeight + ") because maximum is "
- + "(" + maxWidth + "x" + maxHeight + ").");
- ((ViewGroup)iconView.getParent()).removeView(iconView);
- }
+ iconView.setImageDrawable(serviceIcon);
}
private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 77990af..8cbb5dc 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -900,9 +900,6 @@
mAgent.doRestoreFinished(mEphemeralOpToken,
backupManagerService.getBackupManagerBinder());
- // Ask the agent for logs after doRestoreFinished() to allow it to finalize its logs.
- BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mCurrentPackage, mAgent);
-
// If we get this far, the callback or timeout will schedule the
// next restore state, so we're done
} catch (Exception e) {
@@ -1323,6 +1320,11 @@
EventLog.writeEvent(EventLogTags.RESTORE_PACKAGE,
mCurrentPackage.packageName, size);
+ // Ask the agent for logs after doRestoreFinished() has completed executing to allow
+ // it to finalize its logs.
+ BackupManagerMonitorUtils.monitorAgentLoggingResults(mMonitor, mCurrentPackage,
+ mAgent);
+
// Just go back to running the restore queue
keyValueAgentCleanup();
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/companion/java/com/android/server/companion/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
index 0457e9a..5a3db4b 100644
--- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
+++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java
@@ -53,8 +53,6 @@
private static final int VERSION = 1;
private static final int HEADER_LENGTH = 6;
- private static final String HANDSHAKE_PROTOCOL = "AES_256_CBC-HMAC_SHA256";
-
private final InputStream mInput;
private final OutputStream mOutput;
private final Callback mCallback;
@@ -62,14 +60,16 @@
private final AttestationVerifier mVerifier;
private volatile boolean mStopped;
- private boolean mInProgress;
+ private volatile boolean mInProgress;
private Role mRole;
+ private byte[] mClientInit;
private D2DHandshakeContext mHandshakeContext;
private D2DConnectionContextV1 mConnectionContext;
private String mAlias;
private int mVerificationResult;
+ private boolean mPskVerified;
/**
@@ -202,8 +202,8 @@
}
try {
- initiateHandshake();
mInProgress = true;
+ initiateHandshake();
} catch (BadHandleException e) {
throw new SecureChannelException("Failed to initiate handshake protocol.", e);
}
@@ -329,12 +329,56 @@
mRole = Role.Initiator;
mHandshakeContext = D2DHandshakeContext.forInitiator();
+ mClientInit = mHandshakeContext.getNextHandshakeMessage();
// Send Client Init
if (DEBUG) {
Slog.d(TAG, "Sending Ukey2 Client Init message");
}
- sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
+ sendMessage(MessageType.HANDSHAKE_INIT, constructHandshakeInitMessage(mClientInit));
+ }
+
+ // In an occasion where both participants try to initiate a handshake, resolve the conflict
+ // with a dice roll simulated by the message byte content comparison.
+ // The higher value wins! (a.k.a. gets to be the initiator)
+ private byte[] handleHandshakeCollision(byte[] handshakeInitMessage)
+ throws IOException, HandshakeException, BadHandleException, CryptoException {
+
+ // First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT
+ ByteBuffer buffer = ByteBuffer.wrap(handshakeInitMessage);
+ boolean isClientInit = buffer.get() == 0;
+ byte[] handshakeMessage = new byte[buffer.remaining()];
+ buffer.get(handshakeMessage);
+
+ // If received message is Server Init or current role is Responder, then there was
+ // no collision. Return extracted handshake message.
+ if (mHandshakeContext == null || !isClientInit) {
+ return handshakeMessage;
+ }
+
+ Slog.w(TAG, "Detected a Ukey2 handshake role collision. Negotiating a role.");
+
+ // if received message is "larger" than the sent message, then reset the handshake context.
+ if (compareByteArray(mClientInit, handshakeMessage) < 0) {
+ Slog.d(TAG, "Assigned: Responder");
+ mHandshakeContext = null;
+ return handshakeMessage;
+ } else {
+ Slog.d(TAG, "Assigned: Initiator; Discarding received Client Init");
+
+ // Wait for another init message after discarding the client init
+ ByteBuffer nextInitMessage = ByteBuffer.wrap(readMessage(MessageType.HANDSHAKE_INIT));
+
+ // Throw if this message is a Client Init again; 0 = CLIENT INIT, 1 = SERVER INIT
+ if (nextInitMessage.get() == 0) {
+ // This should never happen!
+ throw new HandshakeException("Failed to resolve Ukey2 handshake role collision.");
+ }
+ byte[] nextHandshakeMessage = new byte[nextInitMessage.remaining()];
+ nextInitMessage.get(nextHandshakeMessage);
+
+ return nextHandshakeMessage;
+ }
}
private void exchangeHandshake()
@@ -345,8 +389,15 @@
}
// Waiting for message
- byte[] handshakeMessage = readMessage(MessageType.HANDSHAKE_INIT);
+ byte[] handshakeInitMessage = readMessage(MessageType.HANDSHAKE_INIT);
+ // Mark "in-progress" upon receiving the first message
+ mInProgress = true;
+
+ // Handle a potential collision where both devices tried to initiate a connection
+ byte[] handshakeMessage = handleHandshakeCollision(handshakeInitMessage);
+
+ // Proceed with the rest of Ukey2 handshake
if (mHandshakeContext == null) { // Server-side logic
mRole = Role.Responder;
mHandshakeContext = D2DHandshakeContext.forResponder();
@@ -361,7 +412,8 @@
if (DEBUG) {
Slog.d(TAG, "Sending Ukey2 Server Init message");
}
- sendMessage(MessageType.HANDSHAKE_INIT, mHandshakeContext.getNextHandshakeMessage());
+ sendMessage(MessageType.HANDSHAKE_INIT,
+ constructHandshakeInitMessage(mHandshakeContext.getNextHandshakeMessage()));
// Receive Client Finish
if (DEBUG) {
@@ -418,9 +470,9 @@
? Role.Responder
: Role.Initiator,
mPreSharedKey);
- boolean authenticated = Arrays.equals(receivedAuthToken, expectedAuthToken);
+ mPskVerified = Arrays.equals(receivedAuthToken, expectedAuthToken);
- if (!authenticated) {
+ if (!mPskVerified) {
throw new SecureChannelException("Failed to verify the hash of pre-shared key.");
}
@@ -477,10 +529,21 @@
}
private boolean isSecured() {
+ // Is ukey-2 encrypted
if (mConnectionContext == null) {
return false;
}
- return mVerifier == null || mVerificationResult == RESULT_SUCCESS;
+ // Is authenticated
+ return mPskVerified || mVerificationResult == RESULT_SUCCESS;
+ }
+
+ // First byte indicates message type; 0 = CLIENT INIT, 1 = SERVER INIT
+ // This information is needed to help resolve potential role collision.
+ private byte[] constructHandshakeInitMessage(byte[] message) {
+ return ByteBuffer.allocate(1 + message.length)
+ .put((byte) (Role.Initiator.equals(mRole) ? 0 : 1))
+ .put(message)
+ .array();
}
private byte[] constructToken(D2DHandshakeContext.Role role, byte[] authValue)
@@ -494,6 +557,22 @@
.array());
}
+ // Arbitrary comparator
+ private int compareByteArray(byte[] a, byte[] b) {
+ if (a == b) {
+ return 0;
+ }
+ if (a.length != b.length) {
+ return a.length - b.length;
+ }
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != b[i]) {
+ return a[i] - b[i];
+ }
+ }
+ return 0;
+ }
+
private String generateAlias() {
String alias;
do {
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 277bd88..2d856b9 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -29,7 +29,6 @@
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Future;
class SecureTransport extends Transport implements SecureChannel.Callback {
private final SecureChannel mSecureChannel;
@@ -70,7 +69,10 @@
@Override
protected void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException {
- establishSecureConnection();
+ // Check if channel is secured; otherwise start securing
+ if (!mShouldProcessRequests) {
+ establishSecureConnection();
+ }
if (DEBUG) {
Slog.d(TAG, "Queueing message 0x" + Integer.toHexString(message)
@@ -90,15 +92,12 @@
}
private void establishSecureConnection() {
- // Check if channel is secured and start securing
- if (!mShouldProcessRequests) {
- Slog.d(TAG, "Establishing secure connection.");
- try {
- mSecureChannel.establishSecureConnection();
- } catch (Exception e) {
- Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
- onError(e);
- }
+ Slog.d(TAG, "Establishing secure connection.");
+ try {
+ mSecureChannel.establishSecureConnection();
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to initiate secure channel handshake.", e);
+ onError(e);
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 392b5df..e01a4f6 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;
@@ -2668,7 +2667,10 @@
+ " code=" + code
+ " callerApp=" + r.app
+ " targetSDK=" + r.app.info.targetSdkVersion
- + " requiredPermissions=" + policyInfo.toPermissionString();
+ + " requiredPermissions=" + policyInfo.toPermissionString()
+ + (policyInfo.hasForegroundOnlyPermission()
+ ? " and the app must be in the eligible state/exemptions"
+ + " to access the foreground only permission" : "");
Slog.wtfQuiet(TAG, msg);
Slog.w(TAG, msg);
} break;
@@ -2678,7 +2680,10 @@
+ " callerApp=" + r.app
+ " targetSDK=" + r.app.info.targetSdkVersion
+ " requires permissions: "
- + policyInfo.toPermissionString());
+ + policyInfo.toPermissionString()
+ + (policyInfo.hasForegroundOnlyPermission()
+ ? " and the app must be in the eligible state/exemptions"
+ + " to access the foreground only permission" : ""));
} break;
case FGS_TYPE_POLICY_CHECK_OK:
default:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index b70c3e4..58c7326 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1100,7 +1100,8 @@
synchronized (rolesMap) {
Pair<Integer, Integer> key = new Pair<>(useCase, role);
if (!rolesMap.containsKey(key)) {
- return AudioSystem.SUCCESS;
+ // trying to clear a role for a device that wasn't set
+ return AudioSystem.BAD_VALUE;
}
final int status = asi.deviceRoleAction(useCase, role, null);
if (status == AudioSystem.SUCCESS) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 24eba76..b3fffbf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3373,8 +3373,13 @@
return;
}
- sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
- direction/*val1*/, flags/*val2*/, callingPackage));
+ final VolumeEvent evt = new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ direction/*val1*/, flags/*val2*/, callingPackage);
+ sVolumeLogger.enqueue(evt);
+ // also logging mute/unmute calls to the dedicated logger
+ if (isMuteAdjust(direction)) {
+ sMuteLogger.enqueue(evt);
+ }
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
callingHasAudioSettingsPermission(), AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
@@ -3475,7 +3480,7 @@
}
// If either the client forces allowing ringer modes for this adjustment,
- // or the stream type is one that is affected by ringer modes
+ // or stream is used for UI sonification
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(isUiSoundsStreamType(streamTypeAlias))) {
int ringerMode = getRingerModeInternal();
@@ -3496,6 +3501,13 @@
if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
}
+ } else if (isStreamMutedByRingerOrZenMode(streamTypeAlias) && streamState.mIsMuted) {
+ // if the stream is currently muted streams by ringer/zen mode
+ // then it cannot be unmuted (without FLAG_ALLOW_RINGER_MODES)
+ if (direction == AudioManager.ADJUST_TOGGLE_MUTE
+ || direction == AudioManager.ADJUST_UNMUTE) {
+ adjustVolume = false;
+ }
}
// If the ringer mode or zen is muting the stream, do not change stream unless
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index 6ad9390..6ebb42e 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -609,7 +609,7 @@
(mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0)
? AudioSystem.STREAM_NAMES[mStreamType]
: ("stream " + mStreamType);
- return new StringBuilder("Error trying to unmute ")
+ return new StringBuilder("Invalid call to unmute ")
.append(streamName)
.append(" despite muted streams 0x")
.append(Integer.toHexString(mRingerZenMutedStreams))
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 0315352..0b04159 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -38,6 +38,7 @@
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.hardware.CameraExtensionSessionStats;
import android.hardware.CameraSessionStats;
import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
@@ -247,6 +248,7 @@
public final int mSessionIndex;
private long mDurationOrStartTimeMs; // Either start time, or duration once completed
+ public CameraExtensionSessionStats mExtSessionStats = null;
CameraUsageEvent(String cameraId, int facing, String clientName, int apiLevel,
boolean isNdk, int action, int latencyMs, int operatingMode, boolean deviceError,
@@ -269,7 +271,7 @@
public void markCompleted(int internalReconfigure, long requestCount,
long resultErrorCount, boolean deviceError,
List<CameraStreamStats> streamStats, String userTag,
- int videoStabilizationMode) {
+ int videoStabilizationMode, CameraExtensionSessionStats extStats) {
if (mCompleted) {
return;
}
@@ -282,6 +284,7 @@
mStreamStats = streamStats;
mUserTag = userTag;
mVideoStabilizationMode = videoStabilizationMode;
+ mExtSessionStats = extStats;
if (CameraServiceProxy.DEBUG) {
Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
" was in use by " + mClientName + " for " +
@@ -825,6 +828,36 @@
Slog.w(TAG, "Unknown camera facing: " + e.mCameraFacing);
}
+ int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE;
+ boolean extensionIsAdvanced = false;
+ if (e.mExtSessionStats != null) {
+ switch (e.mExtSessionStats.type) {
+ case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC:
+ extensionType = FrameworkStatsLog
+ .CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_AUTOMATIC;
+ break;
+ case CameraExtensionSessionStats.Type.EXTENSION_FACE_RETOUCH:
+ extensionType = FrameworkStatsLog
+ .CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_FACE_RETOUCH;
+ break;
+ case CameraExtensionSessionStats.Type.EXTENSION_BOKEH:
+ extensionType =
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_BOKEH;
+ break;
+ case CameraExtensionSessionStats.Type.EXTENSION_HDR:
+ extensionType =
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_HDR;
+ break;
+ case CameraExtensionSessionStats.Type.EXTENSION_NIGHT:
+ extensionType =
+ FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NIGHT;
+ break;
+ default:
+ Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type);
+ }
+ extensionIsAdvanced = e.mExtSessionStats.isAdvanced;
+ }
+
int streamCount = 0;
if (e.mStreamStats != null) {
streamCount = e.mStreamStats.size();
@@ -847,7 +880,9 @@
+ ", userTag is " + e.mUserTag
+ ", videoStabilizationMode " + e.mVideoStabilizationMode
+ ", logId " + e.mLogId
- + ", sessionIndex " + e.mSessionIndex);
+ + ", sessionIndex " + e.mSessionIndex
+ + ", mExtSessionStats {type " + extensionType
+ + " isAdvanced " + extensionIsAdvanced + "}");
}
// Convert from CameraStreamStats to CameraStreamProto
CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS];
@@ -907,7 +942,8 @@
MessageNano.toByteArray(streamProtos[2]),
MessageNano.toByteArray(streamProtos[3]),
MessageNano.toByteArray(streamProtos[4]),
- e.mUserTag, e.mVideoStabilizationMode, e.mLogId, e.mSessionIndex);
+ e.mUserTag, e.mVideoStabilizationMode, e.mLogId, e.mSessionIndex,
+ extensionType, extensionIsAdvanced);
}
}
@@ -1098,6 +1134,7 @@
int videoStabilizationMode = cameraState.getVideoStabilizationMode();
long logId = cameraState.getLogId();
int sessionIdx = cameraState.getSessionIndex();
+ CameraExtensionSessionStats extSessionStats = cameraState.getExtensionSessionStats();
synchronized(mLock) {
// Update active camera list and notify NFC if necessary
boolean wasEmpty = mActiveCameraUsage.isEmpty();
@@ -1152,7 +1189,8 @@
Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
oldEvent.markCompleted(/*internalReconfigure*/0, /*requestCount*/0,
/*resultErrorCount*/0, /*deviceError*/false, streamStats,
- /*userTag*/"", /*videoStabilizationMode*/-1);
+ /*userTag*/"", /*videoStabilizationMode*/-1,
+ new CameraExtensionSessionStats());
mCameraUsageHistory.add(oldEvent);
}
break;
@@ -1163,7 +1201,7 @@
doneEvent.markCompleted(internalReconfigureCount, requestCount,
resultErrorCount, deviceError, streamStats, userTag,
- videoStabilizationMode);
+ videoStabilizationMode, extSessionStats);
mCameraUsageHistory.add(doneEvent);
// Do not double count device error
deviceError = false;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 5d0177b..eb7fa10 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -205,7 +205,7 @@
@VisibleForTesting
DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy,
- @NonNull SystemPropertySetter systemPropertySetter) {
+ @NonNull SystemPropertySetter systemPropertySetter) {
super(context);
mSystemPropertySetter = systemPropertySetter;
// We use the DisplayThread because this service indirectly drives
@@ -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);
}
@@ -391,12 +395,7 @@
setRearDisplayStateLocked();
- if (!mPendingState.isPresent()) {
- // If the change in the supported states didn't result in a change of the pending
- // state commitPendingState() will never be called and the callbacks will never be
- // notified of the change.
- notifyDeviceStateInfoChangedAsync();
- }
+ notifyDeviceStateInfoChangedAsync();
mHandler.post(this::notifyPolicyIfNeeded);
}
@@ -460,12 +459,7 @@
mOverrideRequestController.handleBaseStateChanged(identifier);
updatePendingStateLocked();
- if (!mPendingState.isPresent()) {
- // If the change in base state didn't result in a change of the pending state
- // commitPendingState() will never be called and the callbacks will never be
- // notified of the change.
- notifyDeviceStateInfoChangedAsync();
- }
+ notifyDeviceStateInfoChangedAsync();
mHandler.post(this::notifyPolicyIfNeeded);
}
@@ -593,6 +587,18 @@
private void notifyDeviceStateInfoChangedAsync() {
synchronized (mLock) {
+ if (mPendingState.isPresent()) {
+ Slog.i(TAG,
+ "Cannot notify device state info change when pending state is present.");
+ return;
+ }
+
+ if (!mBaseState.isPresent() || !mCommittedState.isPresent()) {
+ Slog.e(TAG, "Cannot notify device state info change before the initial state has"
+ + " been committed.");
+ return;
+ }
+
if (mProcessRecords.size() == 0) {
return;
}
@@ -658,7 +664,7 @@
}
} else {
throw new IllegalArgumentException(
- "Unknown OverrideRest type: " + request.getRequestType());
+ "Unknown OverrideRest type: " + request.getRequestType());
}
boolean updatedPendingState = updatePendingStateLocked();
@@ -713,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) {
@@ -1126,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/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index acfa665..e5ffa7e 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -53,7 +53,6 @@
import android.net.NetworkStack;
import android.net.NetworkStats;
import android.net.RouteInfo;
-import android.net.UidRangeParcel;
import android.net.util.NetdService;
import android.os.BatteryStats;
import android.os.Binder;
@@ -97,7 +96,6 @@
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -1351,70 +1349,6 @@
}
}
- private void closeSocketsForFirewallChainLocked(int chain, String chainName) {
- // UID ranges to close sockets on.
- UidRangeParcel[] ranges;
- // UID ranges whose sockets we won't touch.
- int[] exemptUids;
-
- int numUids = 0;
- if (DBG) Slog.d(TAG, "Closing sockets after enabling chain " + chainName);
- if (getFirewallType(chain) == FIREWALL_ALLOWLIST) {
- // Close all sockets on all non-system UIDs...
- ranges = new UidRangeParcel[] {
- // TODO: is there a better way of finding all existing users? If so, we could
- // specify their ranges here.
- new UidRangeParcel(Process.FIRST_APPLICATION_UID, Integer.MAX_VALUE),
- };
- // ... except for the UIDs that have allow rules.
- synchronized (mRulesLock) {
- final SparseIntArray rules = getUidFirewallRulesLR(chain);
- exemptUids = new int[rules.size()];
- for (int i = 0; i < exemptUids.length; i++) {
- if (rules.valueAt(i) == FIREWALL_RULE_ALLOW) {
- exemptUids[numUids] = rules.keyAt(i);
- numUids++;
- }
- }
- }
- // Normally, allowlist chains only contain deny rules, so numUids == exemptUids.length.
- // But the code does not guarantee this in any way, and at least in one case - if we add
- // a UID rule to the firewall, and then disable the firewall - the chains can contain
- // the wrong type of rule. In this case, don't close connections that we shouldn't.
- //
- // TODO: tighten up this code by ensuring we never set the wrong type of rule, and
- // fix setFirewallEnabled to grab mQuotaLock and clear rules.
- if (numUids != exemptUids.length) {
- exemptUids = Arrays.copyOf(exemptUids, numUids);
- }
- } else {
- // Close sockets for every UID that has a deny rule...
- synchronized (mRulesLock) {
- final SparseIntArray rules = getUidFirewallRulesLR(chain);
- ranges = new UidRangeParcel[rules.size()];
- for (int i = 0; i < ranges.length; i++) {
- if (rules.valueAt(i) == FIREWALL_RULE_DENY) {
- int uid = rules.keyAt(i);
- ranges[numUids] = new UidRangeParcel(uid, uid);
- numUids++;
- }
- }
- }
- // As above; usually numUids == ranges.length, but not always.
- if (numUids != ranges.length) {
- ranges = Arrays.copyOf(ranges, numUids);
- }
- // ... with no exceptions.
- exemptUids = new int[0];
- }
-
- try {
- mNetdService.socketDestroy(ranges, exemptUids);
- } catch(RemoteException | ServiceSpecificException e) {
- Slog.e(TAG, "Error closing sockets after enabling chain " + chainName + ": " + e);
- }
- }
-
@Override
public void setFirewallChainEnabled(int chain, boolean enable) {
enforceSystemUid();
@@ -1439,14 +1373,6 @@
} catch (RuntimeException e) {
throw new IllegalStateException(e);
}
-
- // Close any sockets that were opened by the affected UIDs. This has to be done after
- // disabling network connectivity, in case they react to the socket close by reopening
- // the connection and race with the iptables commands that enable the firewall. All
- // allowlist and denylist chains allow RSTs through.
- if (enable) {
- closeSocketsForFirewallChainLocked(chain, chainName);
- }
}
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1aa1fd1..a3866ca 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2310,6 +2310,9 @@
if ((intent.getFlags() & Intent.FLAG_IGNORE_EPHEMERAL) != 0) {
return false;
}
+ if ((intent.getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER) != 0) {
+ return false;
+ }
if (!skipPackageCheck && intent.getPackage() != null) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index a610b5b..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;
@@ -147,7 +148,7 @@
if (launchMainActivity) {
launchIntent.setAction(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- if (targetTask == null) {
+ if (targetTask == null || options != null) {
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
} else {
@@ -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/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index e18b2e8..06db5be 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -324,7 +324,6 @@
InstallSource installSource = request.getInstallSource();
final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
final boolean pkgAlreadyExists = oldPkgSetting != null;
- final boolean isAllowUpdateOwnership = parsedPackage.isAllowUpdateOwnership();
final String oldUpdateOwner =
pkgAlreadyExists ? oldPkgSetting.getInstallSource().mUpdateOwnerPackageName : null;
final String updateOwnerFromSysconfig = isApex || !pkgSetting.isSystem() ? null
@@ -346,11 +345,7 @@
}
// Handle the update ownership enforcement for APK
- if (!isAllowUpdateOwnership) {
- // If the app wants to opt-out of the update ownership enforcement via manifest,
- // it overrides the installer's use of #setRequestUpdateOwnership.
- installSource = installSource.setUpdateOwnerPackageName(null);
- } else if (!isApex) {
+ if (!isApex) {
// User installer UID as "current" userId if present; otherwise, use the userId
// from InstallRequest.
final int userId = installSource.mInstallerPackageUid != Process.INVALID_UID
@@ -391,22 +386,18 @@
// For non-standard install (addForInit), installSource is null.
} else if (pkgSetting.isSystem()) {
// We still honor the manifest attr if the system app wants to opt-out of it.
- if (!isAllowUpdateOwnership) {
- pkgSetting.setUpdateOwnerPackage(null);
- } else {
- final boolean isSameUpdateOwner = isUpdateOwnershipEnabled
- && TextUtils.equals(oldUpdateOwner, updateOwnerFromSysconfig);
+ final boolean isSameUpdateOwner = isUpdateOwnershipEnabled
+ && TextUtils.equals(oldUpdateOwner, updateOwnerFromSysconfig);
- // Here we handle the update owner for the system package, and the rules are:
- // -. We use the update owner from sysconfig as the initial value.
- // -. Once an app becomes to system app later via OTA, only retains the update
- // owner if it's consistence with sysconfig.
- // -. Clear the update owner when update owner changes from sysconfig.
- if (!pkgAlreadyExists || isSameUpdateOwner) {
- pkgSetting.setUpdateOwnerPackage(updateOwnerFromSysconfig);
- } else {
- pkgSetting.setUpdateOwnerPackage(null);
- }
+ // Here we handle the update owner for the system package, and the rules are:
+ // -. We use the update owner from sysconfig as the initial value.
+ // -. Once an app becomes to system app later via OTA, only retains the update
+ // owner if it's consistence with sysconfig.
+ // -. Clear the update owner when update owner changes from sysconfig.
+ if (!pkgAlreadyExists || isSameUpdateOwner) {
+ pkgSetting.setUpdateOwnerPackage(updateOwnerFromSysconfig);
+ } else {
+ pkgSetting.setUpdateOwnerPackage(null);
}
}
@@ -606,8 +597,8 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
- @PackageManager.InstallFlags int installFlags,
+ public Pair<Integer, IntentSender> installExistingPackageAsUser(@Nullable String packageName,
+ @UserIdInt int userId, @PackageManager.InstallFlags int installFlags,
@PackageManager.InstallReason int installReason,
@Nullable List<String> allowlistedRestrictedPermissions,
@Nullable IntentSender intentSender) {
@@ -632,7 +623,7 @@
true /* requireFullPermission */, true /* checkShell */,
"installExistingPackage for user " + userId);
if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
- return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+ return Pair.create(PackageManager.INSTALL_FAILED_USER_RESTRICTED, intentSender);
}
final long callingId = Binder.clearCallingIdentity();
@@ -648,7 +639,7 @@
final Computer snapshot = mPm.snapshotComputer();
pkgSetting = mPm.mSettings.getPackageLPr(packageName);
if (pkgSetting == null || pkgSetting.getPkg() == null) {
- return PackageManager.INSTALL_FAILED_INVALID_URI;
+ return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
}
if (!snapshot.canViewInstantApps(callingUid, UserHandle.getUserId(callingUid))) {
// only allow the existing package to be used if it's installed as a full
@@ -661,7 +652,7 @@
}
}
if (!installAllowed) {
- return PackageManager.INSTALL_FAILED_INVALID_URI;
+ return Pair.create(PackageManager.INSTALL_FAILED_INVALID_URI, intentSender);
}
}
if (!pkgSetting.getInstalled(userId)) {
@@ -719,14 +710,17 @@
}
// start async restore with no post-install since we finish install here
+ final IntentSender onCompleteSender = intentSender;
+ intentSender = null;
+
InstallRequest request = new InstallRequest(userId,
PackageManager.INSTALL_SUCCEEDED, pkgSetting.getPkg(), new int[]{ userId },
() -> {
mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
userId);
- if (intentSender != null) {
- onRestoreComplete(PackageManager.INSTALL_SUCCEEDED, mContext,
- intentSender);
+ if (onCompleteSender != null) {
+ onInstallComplete(PackageManager.INSTALL_SUCCEEDED, mContext,
+ onCompleteSender);
}
});
restoreAndPostInstall(request);
@@ -735,10 +729,10 @@
Binder.restoreCallingIdentity(callingId);
}
- return PackageManager.INSTALL_SUCCEEDED;
+ return Pair.create(PackageManager.INSTALL_SUCCEEDED, intentSender);
}
- private static void onRestoreComplete(int returnCode, Context context, IntentSender target) {
+ static void onInstallComplete(int returnCode, Context context, IntentSender target) {
Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
PackageManager.installStatusToPublicStatus(returnCode));
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1641d61..6491fd1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1293,8 +1293,15 @@
public void installExistingPackage(String packageName, int installFlags, int installReason,
IntentSender statusReceiver, int userId, List<String> allowListedPermissions) {
final InstallPackageHelper installPackageHelper = new InstallPackageHelper(mPm);
- installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
- installReason, allowListedPermissions, statusReceiver);
+
+ var result = installPackageHelper.installExistingPackageAsUser(packageName, userId,
+ installFlags, installReason, allowListedPermissions, statusReceiver);
+
+ int returnCode = result.first;
+ IntentSender onCompleteSender = result.second;
+ if (onCompleteSender != null) {
+ InstallPackageHelper.onInstallComplete(returnCode, mContext, onCompleteSender);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d3f7002..f0e3895 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -745,9 +745,6 @@
@GuardedBy("mLock")
private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION;
- @GuardedBy("mLock")
- private boolean mAllowsUpdateOwnership = true;
-
private static final FileFilter sAddedApkFilter = new FileFilter() {
@Override
public boolean accept(File file) {
@@ -869,11 +866,13 @@
private static final int USER_ACTION_NOT_NEEDED = 0;
private static final int USER_ACTION_REQUIRED = 1;
+ private static final int USER_ACTION_PENDING_APK_PARSING = 2;
private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER = 3;
@IntDef({
USER_ACTION_NOT_NEEDED,
USER_ACTION_REQUIRED,
+ USER_ACTION_PENDING_APK_PARSING,
USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER,
})
@interface UserActionRequirement {}
@@ -964,11 +963,11 @@
&& !isApexSession()
&& !isUpdateOwner
&& !isInstallerShell
- && mAllowsUpdateOwnership
// We don't enforce the update ownership for the managed user and profile.
&& !isFromManagedUserOrProfile) {
return USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER;
}
+
if (isPermissionGranted) {
return USER_ACTION_NOT_NEEDED;
}
@@ -983,20 +982,7 @@
&& isUpdateWithoutUserActionPermissionGranted
&& ((isUpdateOwnershipEnforcementEnabled ? isUpdateOwner
: isInstallerOfRecord) || isSelfUpdate)) {
- if (!isApexSession()) {
- if (!isTargetSdkConditionSatisfied(this)) {
- return USER_ACTION_REQUIRED;
- }
-
- if (!mSilentUpdatePolicy.isSilentUpdateAllowed(
- getInstallerPackageName(), getPackageName())) {
- // Fall back to the non-silent update if a repeated installation is invoked
- // within the throttle time.
- return USER_ACTION_REQUIRED;
- }
- mSilentUpdatePolicy.track(getInstallerPackageName(), getPackageName());
- return USER_ACTION_NOT_NEEDED;
- }
+ return USER_ACTION_PENDING_APK_PARSING;
}
return USER_ACTION_REQUIRED;
@@ -2404,6 +2390,26 @@
session.sendPendingUserActionIntent(target);
return true;
}
+
+ if (!session.isApexSession() && userActionRequirement == USER_ACTION_PENDING_APK_PARSING) {
+ if (!isTargetSdkConditionSatisfied(session)) {
+ session.sendPendingUserActionIntent(target);
+ return true;
+ }
+
+ if (session.params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) {
+ if (!session.mSilentUpdatePolicy.isSilentUpdateAllowed(
+ session.getInstallerPackageName(), session.getPackageName())) {
+ // Fall back to the non-silent update if a repeated installation is invoked
+ // within the throttle time.
+ session.sendPendingUserActionIntent(target);
+ return true;
+ }
+ session.mSilentUpdatePolicy.track(session.getInstallerPackageName(),
+ session.getPackageName());
+ }
+ }
+
return false;
}
@@ -3409,8 +3415,6 @@
// {@link PackageLite#getTargetSdk()}
mValidatedTargetSdk = packageLite.getTargetSdk();
- mAllowsUpdateOwnership = packageLite.isAllowUpdateOwnership();
-
return packageLite;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 0c52bb5..ae520c0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5350,7 +5350,7 @@
public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
int installReason, List<String> whiteListedPermissions) {
return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
- installReason, whiteListedPermissions, null);
+ installReason, whiteListedPermissions, null).first;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index d3f3a69..05bfec4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -698,7 +698,7 @@
null /* usesSplitNames */, null /* configForSplit */,
null /* splitApkPaths */, null /* splitRevisionCodes */,
apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */,
- null /* splitTypes */, apkLite.isAllowUpdateOwnership());
+ null /* splitTypes */);
sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite,
params.sessionParams.abiOverride, fd.getFileDescriptor());
} catch (IOException e) {
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/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index de31b46..f036835 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -1810,11 +1810,6 @@
}
@Override
- public boolean isAllowUpdateOwnership() {
- return getBoolean2(Booleans2.ALLOW_UPDATE_OWNERSHIP);
- }
-
- @Override
public boolean isVmSafeMode() {
return getBoolean(Booleans.VM_SAFE_MODE);
}
@@ -2518,11 +2513,6 @@
}
@Override
- public PackageImpl setAllowUpdateOwnership(boolean value) {
- return setBoolean2(Booleans2.ALLOW_UPDATE_OWNERSHIP, value);
- }
-
- @Override
public PackageImpl sortActivities() {
Collections.sort(this.activities, ORDER_COMPARATOR);
return this;
@@ -3736,6 +3726,5 @@
private static final long STUB = 1L;
private static final long APEX = 1L << 1;
- private static final long ALLOW_UPDATE_OWNERSHIP = 1L << 2;
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 2fdda12..e54f34d 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1483,10 +1483,4 @@
* @hide
*/
boolean isVisibleToInstantApps();
-
- /**
- * @see R.styleable#AndroidManifest_allowUpdateOwnership
- * @hide
- */
- boolean isAllowUpdateOwnership();
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 6cb6a97..7fc3356 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -388,8 +388,6 @@
ParsingPackage setLocaleConfigResourceId(int localeConfigRes);
- ParsingPackage setAllowUpdateOwnership(boolean value);
-
/**
* Sets the trusted host certificates of apps that are allowed to embed activities of this
* application.
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index fda44e4..1567af0 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -219,7 +219,6 @@
public static final int PARSE_DEFAULT_INSTALL_LOCATION =
PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
public static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
- public static final boolean PARSE_DEFAULT_ALLOW_UPDATE_OWNERSHIP = true;
/**
* If set to true, we will only allow package files that exactly match the DTD. Otherwise, we
@@ -887,9 +886,7 @@
.setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,
R.styleable.AndroidManifest_targetSandboxVersion, sa))
/* Set the global "on SD card" flag */
- .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0)
- .setAllowUpdateOwnership(bool(PARSE_DEFAULT_ALLOW_UPDATE_OWNERSHIP,
- R.styleable.AndroidManifest_allowUpdateOwnership, sa));
+ .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
boolean foundApp = false;
final int depth = parser.getDepth();
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 d93778e..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;
@@ -8445,21 +8466,23 @@
private void updateResolvedBoundsPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ if (resolvedBounds.isEmpty()) {
+ return;
+ }
final Rect screenResolvedBounds =
mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- if (resolvedBounds.isEmpty()) {
- return;
- }
+ final float screenResolvedBoundsWidth = screenResolvedBounds.width();
+ final float parentAppBoundsWidth = parentAppBounds.width();
// Horizontal position
int offsetX = 0;
- if (parentBounds.width() != screenResolvedBounds.width()) {
- if (screenResolvedBounds.width() <= parentAppBounds.width()) {
+ if (parentBounds.width() != screenResolvedBoundsWidth) {
+ if (screenResolvedBoundsWidth <= parentAppBoundsWidth) {
float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
newParentConfiguration);
- offsetX = Math.max(0, (int) Math.ceil((parentAppBounds.width()
- - screenResolvedBounds.width()) * positionMultiplier)
+ offsetX = Math.max(0, (int) Math.ceil((parentAppBoundsWidth
+ - screenResolvedBoundsWidth) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
@@ -8467,14 +8490,21 @@
}
}
+ final float parentAppBoundsHeight = parentAppBounds.height();
+ final float parentBoundsHeight = parentBounds.height();
+ final float screenResolvedBoundsHeight = screenResolvedBounds.height();
// Vertical position
int offsetY = 0;
- if (parentBounds.height() != screenResolvedBounds.height()) {
- if (screenResolvedBounds.height() <= parentAppBounds.height()) {
+ if (parentBoundsHeight != screenResolvedBoundsHeight) {
+ if (screenResolvedBoundsHeight <= parentAppBoundsHeight) {
float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
newParentConfiguration);
- offsetY = Math.max(0, (int) Math.ceil((parentAppBounds.height()
- - screenResolvedBounds.height()) * positionMultiplier)
+ // If in immersive mode, always align to bottom and overlap bottom insets (nav bar,
+ // task bar) as they are transient and hidden. This removes awkward bottom spacing.
+ final float newHeight = mDisplayContent.getDisplayPolicy().isImmersiveMode()
+ ? parentBoundsHeight : parentAppBoundsHeight;
+ offsetY = Math.max(0, (int) Math.ceil((newHeight
+ - screenResolvedBoundsHeight) * positionMultiplier)
// This is added to make sure that insets added inside
// CompatDisplayInsets#getContainerBounds() do not break the alignment
// provided by the positionMultiplier
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 899c36c..77e70a2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1254,6 +1254,10 @@
return mNavigationBar;
}
+ boolean isImmersiveMode() {
+ return mIsImmersiveMode;
+ }
+
/**
* Control the animation to run when a window's state changes. Return a positive number to
* force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
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 b557b2e..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}.
*/
@@ -1311,6 +1362,16 @@
final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+ // In case of translucent activities we check if the requested size is different from
+ // the size provided using inherited bounds. In that case we decide to not apply rounded
+ // corners because we assume the specific layout would. This is the case when the layout
+ // of the translucent activity uses only a part of all the bounds because of the use of
+ // LayoutParams.WRAP_CONTENT.
+ if (hasInheritedLetterboxBehavior() && (cropBounds.width() != mainWindow.mRequestedWidth
+ || cropBounds.height() != mainWindow.mRequestedHeight)) {
+ return null;
+ }
+
// It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
// because taskbar bounds used in {@link #adjustBoundsIfNeeded}
// are in screen coordinates
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b7c29bf..bb6f805 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5673,6 +5673,7 @@
}
mTransitionController.requestStartTransition(transition, tr,
null /* remoteTransition */, null /* displayChange */);
+ mTransitionController.collect(tr);
moveTaskToBackInner(tr);
});
} else {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index d1ee109..c763cfa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -21,6 +21,8 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -959,8 +961,20 @@
true /* beforeStopping */)) {
return false;
}
- return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
- false /* fromClient */, true /* isAutoEnter */);
+ final int prevMode = ar.getTask().getWindowingMode();
+ final boolean inPip = mController.mAtm.enterPictureInPictureMode(ar,
+ ar.pictureInPictureArgs, false /* fromClient */, true /* isAutoEnter */);
+ final int currentMode = ar.getTask().getWindowingMode();
+ if (prevMode == WINDOWING_MODE_FULLSCREEN && currentMode == WINDOWING_MODE_PINNED
+ && mTransientLaunches != null
+ && ar.mDisplayContent.hasTopFixedRotationLaunchingApp()) {
+ // There will be a display configuration change after finishing this transition.
+ // Skip dispatching the change for PiP task to avoid its activity drawing for the
+ // intermediate state which will cause flickering. The final PiP bounds in new
+ // rotation will be applied by PipTransition.
+ ar.mDisplayContent.mPinnedTaskController.setEnterPipTransaction(null);
+ }
+ return inPip;
}
// Legacy pip-entry (not via isAutoEnterEnabled).
@@ -1456,6 +1470,9 @@
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || ar.getTask() == null
|| ar.getTask().isVisibleRequested()) continue;
+ final ChangeInfo change = mChanges.get(ar);
+ // Intentionally skip record snapshot for changes originated from PiP.
+ if (change != null && change.mWindowingMode == WINDOWING_MODE_PINNED) continue;
mController.mSnapshotController.mTaskSnapshotController.recordSnapshot(
ar.getTask(), false /* allowSnapshotHome */);
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e228e7e..1c6a412 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -516,8 +516,14 @@
* playing, but can be "opened-up" for certain transition operations like calculating layers
* for finishTransaction.
*/
- boolean canAssignLayers() {
- return mBuildingFinishLayers || !isPlaying();
+ boolean canAssignLayers(@NonNull WindowContainer wc) {
+ // Don't build window state into finish transaction in case another window is added or
+ // removed during transition playing.
+ if (mBuildingFinishLayers) {
+ return wc.asWindowState() == null;
+ }
+ // Always allow WindowState to assign layers since it won't affect transition.
+ return wc.asWindowState() != null || !isPlaying();
}
@WindowConfiguration.WindowingMode
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index be5f141..0152666 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2596,7 +2596,7 @@
void assignLayer(Transaction t, int layer) {
// Don't assign layers while a transition animation is playing
// TODO(b/173528115): establish robust best-practices around z-order fighting.
- if (!mTransitionController.canAssignLayers()) return;
+ if (!mTransitionController.canAssignLayers(this)) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
setLayer(t, layer);
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index c494044..a3eb390 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -418,7 +418,7 @@
::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
const AidlTvMessageEvent& event) {
const std::string DEVICE_ID_SUBTYPE = "device_id";
- if (sizeof(event.messages) > 0 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
+ if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
mHal->mLooper
->sendMessage(new NotifyTvMessageHandler(mHal,
TvMessageEventWrapper::createEventWrapper(
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 9b0a1cb0..3c1432a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -150,7 +150,7 @@
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
- && isProviderAllowed(option, info.getComponentName())
+ && isProviderAllowed(option, info)
&& checkSystemProviderRequirement(option, info.isSystemProvider())) {
Slog.i(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ "conditions");
@@ -167,9 +167,14 @@
return null;
}
- private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
+ private static boolean isProviderAllowed(CredentialOption option,
+ CredentialProviderInfo providerInfo) {
+ if (providerInfo.isSystemProvider()) {
+ // Always allow system providers , including the remote provider
+ return true;
+ }
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
- componentName)) {
+ providerInfo.getComponentName())) {
Slog.i(TAG, "Provider allow list specified but does not contain this provider");
return false;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 1322225..df968f9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,17 +27,22 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppGlobals;
import android.app.BroadcastOptions;
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;
@@ -45,6 +50,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.TelephonyManager;
@@ -1015,20 +1021,90 @@
/**
* Handles internal state related to packages getting updated.
*/
- void handlePackageChanged(@Nullable String updatedPackage, int userId, boolean packageRemoved) {
- if (updatedPackage == null) {
- return;
- }
- if (packageRemoved) {
+ void handlePackageChanged(
+ @Nullable String updatedPackage, int userId, @Nullable String removedDpcPackage) {
+ Binder.withCleanCallingIdentity(() -> {
Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId);
- for (EnforcingAdmin admin : admins) {
- if (admin.getPackageName().equals(updatedPackage)) {
- // remove policies for the uninstalled package
- removePoliciesForAdmin(admin);
+ if (removedDpcPackage != null) {
+ for (EnforcingAdmin admin : admins) {
+ if (removedDpcPackage.equals(admin.getPackageName())) {
+ removePoliciesForAdmin(admin);
+ return;
+ }
}
}
- } else {
- updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+ for (EnforcingAdmin admin : admins) {
+ if (updatedPackage == null || updatedPackage.equals(admin.getPackageName())) {
+ if (!isPackageInstalled(admin.getPackageName(), userId)) {
+ Slogf.i(TAG, String.format(
+ "Admin package %s not found for user %d, removing admin policies",
+ admin.getPackageName(), userId));
+ // remove policies for the uninstalled package
+ removePoliciesForAdmin(admin);
+ return;
+ }
+ }
+ }
+ 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(
+ packageName, 0, userId) != null;
+ } catch (RemoteException re) {
+ // Shouldn't happen.
+ Slogf.wtf(TAG, "Error handling package changes", re);
+ return true;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 324e260..f875e34 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -232,7 +232,6 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
-import static android.provider.DeviceConfig.NAMESPACE_TELEPHONY;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -876,10 +875,6 @@
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
public static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = true;
- private static final String ENABLE_WORK_PROFILE_TELEPHONY_FLAG =
- "enable_work_profile_telephony";
- private static final boolean DEFAULT_WORK_PROFILE_TELEPHONY_FLAG = false;
-
// TODO(b/261999445) remove the flag after rollout.
private static final String HEADLESS_FLAG = "headless";
private static final boolean DEFAULT_HEADLESS_FLAG = true;
@@ -1389,6 +1384,7 @@
private void handlePackagesChanged(@Nullable String packageName, int userHandle) {
boolean removedAdmin = false;
+ String removedAdminPackage = null;
if (VERBOSE_LOG) {
Slogf.d(LOG_TAG, "Handling package changes package " + packageName
+ " for user " + userHandle);
@@ -1411,6 +1407,7 @@
"Admin package %s not found for user %d, removing active admin",
packageName, userHandle));
removedAdmin = true;
+ removedAdminPackage = adminPackage;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
pushActiveAdminPackagesLocked(userHandle);
@@ -1444,7 +1441,8 @@
startOwnerService(userHandle, "package-broadcast");
}
if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
- mDevicePolicyEngine.handlePackageChanged(packageName, userHandle, removedAdmin);
+ mDevicePolicyEngine.handlePackageChanged(
+ packageName, userHandle, removedAdminPackage);
}
// Persist updates if the removed package was an admin or delegate.
if (removedAdmin || removedDelegate) {
@@ -3376,9 +3374,7 @@
onLockSettingsReady();
loadAdminDataAsync();
mOwners.systemReady();
- if (isWorkProfileTelephonyEnabled()) {
- applyManagedSubscriptionsPolicyIfRequired();
- }
+ applyManagedSubscriptionsPolicyIfRequired();
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
synchronized (getLockObject()) {
@@ -3409,9 +3405,7 @@
unregisterOnSubscriptionsChangedListener();
int policyType = getManagedSubscriptionsPolicy().getPolicyType();
if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS) {
- final int parentUserId = getProfileParentId(copeProfileUserId);
- // By default, assign all current and future subs to system user on COPE devices.
- registerListenerToAssignSubscriptionsToUser(parentUserId);
+ clearManagedSubscriptionsPolicy();
} else if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
// Add listener to assign all current and future subs to managed profile.
registerListenerToAssignSubscriptionsToUser(copeProfileUserId);
@@ -3541,7 +3535,9 @@
deleteTransferOwnershipBundleLocked(metadata.userId);
}
updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
- pushUserControlDisabledPackagesLocked(metadata.userId);
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ pushUserControlDisabledPackagesLocked(metadata.userId);
+ }
}
private void maybeLogStart() {
@@ -7718,11 +7714,10 @@
}
mLockSettingsInternal.refreshStrongAuthTimeout(parentId);
- if (isWorkProfileTelephonyEnabled()) {
- clearManagedSubscriptionsPolicy();
- clearLauncherShortcutOverrides();
- updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
- }
+ clearManagedSubscriptionsPolicy();
+ clearLauncherShortcutOverrides();
+ updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
+
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
}
@@ -10114,7 +10109,9 @@
clearUserPoliciesLocked(userId);
clearOverrideApnUnchecked();
clearApplicationRestrictions(userId);
- mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
+ if (!isPolicyEngineForFinanceFlagEnabled()) {
+ mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
+ }
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -10966,8 +10963,13 @@
/* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(),
isAdb(caller), hasIncompatibleAccountsOrNonAdb);
if (code != STATUS_OK) {
- throw new IllegalStateException(computeProvisioningErrorStringLocked(code,
- deviceOwnerUserId, owner, showComponentOnError));
+ final String provisioningErrorStringLocked = computeProvisioningErrorStringLocked(code,
+ deviceOwnerUserId, owner, showComponentOnError);
+ if (code == STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED) {
+ throw new ServiceSpecificException(code, provisioningErrorStringLocked);
+ } else {
+ throw new IllegalStateException(provisioningErrorStringLocked);
+ }
}
}
@@ -11021,6 +11023,9 @@
case STATUS_HAS_PAIRED:
return "Not allowed to set the device owner because this device has already "
+ "paired.";
+ case STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED:
+ return "Cannot provision an unsupported DPC into DO on a"
+ + " headless device";
default:
return "Unexpected @ProvisioningPreCondition: " + code;
}
@@ -11334,11 +11339,10 @@
synchronized (mSubscriptionsChangedListenerLock) {
pw.println("Subscription changed listener : " + mSubscriptionsChangedListener);
}
- pw.println("DPM Flag enable_work_profile_telephony : "
- + isWorkProfileTelephonyDevicePolicyManagerFlagEnabled());
- pw.println("Telephony Flag enable_work_profile_telephony : "
- + isWorkProfileTelephonySubscriptionManagerFlagEnabled());
+ pw.println("DPM global setting ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS : "
+ + mInjector.settingsGlobalGetString(
+ Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS));
mHandler.post(() -> handleDump(pw));
dumpResources(pw);
}
@@ -11443,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,
@@ -11580,6 +11588,15 @@
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller));
}
+
+ if (!parent && isManagedProfile(caller.getUserId())
+ && getManagedSubscriptionsPolicy().getPolicyType()
+ != ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ throw new IllegalStateException(
+ "Default sms application can only be set on the profile, when "
+ + "ManagedSubscriptions policy is set");
+ }
+
if (parent) {
userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -13376,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()) {
@@ -13901,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()
@@ -15197,8 +15207,14 @@
@Override
public void setGlobalSetting(ComponentName who, String setting, String value) {
- Objects.requireNonNull(who, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(who);
+ if (Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS.equals(setting)) {
+ Preconditions.checkCallAuthorization(isCallerDevicePolicyManagementRoleHolder(caller));
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutString(setting, value));
+ return;
+ }
+ Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
DevicePolicyEventLogger
@@ -16066,8 +16082,8 @@
}
@Override
- public List<String> getAllCrossProfilePackages() {
- return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+ public List<String> getAllCrossProfilePackages(int userId) {
+ return DevicePolicyManagerService.this.getAllCrossProfilePackages(userId);
}
@Override
@@ -20286,7 +20302,7 @@
}
@Override
- public List<String> getAllCrossProfilePackages() {
+ public List<String> getAllCrossProfilePackages(int userId) {
if (!mHasFeature) {
return Collections.emptyList();
}
@@ -20295,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());
@@ -20327,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) {
@@ -23791,26 +23806,6 @@
suspendAppsForQuietProfiles(keepProfileRunning);
}
- private boolean isWorkProfileTelephonyEnabled() {
- return isWorkProfileTelephonyDevicePolicyManagerFlagEnabled()
- && isWorkProfileTelephonySubscriptionManagerFlagEnabled();
- }
-
- private boolean isWorkProfileTelephonyDevicePolicyManagerFlagEnabled() {
- return DeviceConfig.getBoolean(NAMESPACE_DEVICE_POLICY_MANAGER,
- ENABLE_WORK_PROFILE_TELEPHONY_FLAG, DEFAULT_WORK_PROFILE_TELEPHONY_FLAG);
- }
-
- private boolean isWorkProfileTelephonySubscriptionManagerFlagEnabled() {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- return DeviceConfig.getBoolean(NAMESPACE_TELEPHONY, ENABLE_WORK_PROFILE_TELEPHONY_FLAG,
- false);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
- }
- }
-
@Override
public void setOverrideKeepProfilesRunning(boolean enabled) {
Preconditions.checkCallAuthorization(
@@ -23921,12 +23916,10 @@
@Override
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
- if (isWorkProfileTelephonyEnabled()) {
- synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
- return admin.mManagedSubscriptionsPolicy;
- }
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getProfileOwnerOfOrganizationOwnedDeviceLocked();
+ if (admin != null && admin.mManagedSubscriptionsPolicy != null) {
+ return admin.mManagedSubscriptionsPolicy;
}
}
return new ManagedSubscriptionsPolicy(
@@ -23935,10 +23928,14 @@
@Override
public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
- if (!isWorkProfileTelephonyEnabled()) {
+ CallerIdentity caller = getCallerIdentity();
+
+ if (!isCallerDevicePolicyManagementRoleHolder(caller)
+ && !Objects.equals(mInjector.settingsGlobalGetString(
+ Global.ALLOW_WORK_PROFILE_TELEPHONY_FOR_NON_DPM_ROLE_HOLDERS), "1")) {
throw new UnsupportedOperationException("This api is not enabled");
}
- CallerIdentity caller = getCallerIdentity();
+
Preconditions.checkCallAuthorization(isProfileOwnerOfOrganizationOwnedDevice(caller),
"This policy can only be set by a profile owner on an organization-owned "
+ "device.");
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 8f23ae4..4007672 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -34,6 +34,8 @@
import android.os.UpdateEngine;
import android.os.UpdateEngineCallback;
import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
import com.android.internal.R;
@@ -332,8 +334,17 @@
Context context = getContext();
BackgroundThread.get().getThreadHandler().post(() -> {
try {
+ int usageSetting = -1;
+ try {
+ // Get "Usage & diagnostics" checkbox status. 1 is for enabled, 0 is for
+ // disabled.
+ usageSetting = Settings.Global.getInt(context.getContentResolver(), "multi_cb");
+ } catch (SettingNotFoundException e) {
+ Log.i(LOG_TAG, "Usage setting not found: " + e.getMessage());
+ }
+
// Prepare profile report
- String reportName = mIProfcollect.report() + ".zip";
+ String reportName = mIProfcollect.report(usageSetting) + ".zip";
if (!context.getResources().getBoolean(
R.bool.config_profcollectReportUploaderEnabled)) {
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/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 6d3cdff..3200871 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -218,7 +218,6 @@
AndroidPackage::isClearUserDataOnFailedRestoreAllowed,
AndroidPackage::isAllowNativeHeapPointerTagging,
AndroidPackage::isTaskReparentingAllowed,
- AndroidPackage::isAllowUpdateOwnership,
AndroidPackage::isBackupInForeground,
AndroidPackage::isHardwareAccelerated,
AndroidPackage::isSaveStateDisallowed,
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 4d06855..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;
@@ -88,6 +92,11 @@
mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider);
mSysPropSetter = new TestSystemPropertySetter();
+ setupDeviceStateManagerService();
+ flushHandler(); // Flush the handler to ensure the initial values are committed.
+ }
+
+ private void setupDeviceStateManagerService() {
mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy,
mSysPropSetter);
@@ -97,8 +106,6 @@
when(mService.mActivityTaskManagerInternal.getTopApp())
.thenReturn(mWindowProcessController);
when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
-
- flushHandler(); // Flush the handler to ensure the initial values are committed.
}
private void flushHandler() {
@@ -264,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 {
@@ -325,6 +344,21 @@
}
@Test
+ public void registerCallback_initialValueUnavailable() 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.
+
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+ // The callback should never be called when the base state is not set yet.
+ assertNull(callback.getLastNotifiedInfo());
+ }
+
+ @Test
public void requestState() throws RemoteException {
TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
mService.getBinderService().registerCallback(callback);
@@ -939,8 +973,18 @@
DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE,
DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
+
+ @Nullable private final DeviceState mInitialState;
private Listener mListener;
+ private TestDeviceStateProvider() {
+ this(DEFAULT_DEVICE_STATE);
+ }
+
+ private TestDeviceStateProvider(@Nullable DeviceState initialState) {
+ mInitialState = initialState;
+ }
+
@Override
public void setListener(Listener listener) {
if (mListener != null) {
@@ -950,7 +994,9 @@
mListener = listener;
mListener.onSupportedDeviceStatesChanged(mSupportedDeviceStates,
SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED);
- mListener.onStateChanged(mSupportedDeviceStates[0].getIdentifier());
+ if (mInitialState != null) {
+ mListener.onStateChanged(mInitialState.getIdentifier());
+ }
}
public void notifySupportedDeviceStates(DeviceState[] supportedDeviceStates) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index de82854..b2843d8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -422,8 +422,7 @@
null /* splitNames */, null /* isFeatureSplits */, null /* usesSplitNames */,
null /* configForSplit */, null /* splitApkPaths */,
null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(),
- null /* requiredSplitTypes */, null /* splitTypes */,
- false /* allowUpdateOwnership */);
+ null /* requiredSplitTypes */, null /* splitTypes */);
Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite));
}
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 81e0fb3..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;
@@ -455,6 +458,29 @@
}
@Test
+ public void testGetCropBoundsIfNeeded_handleCropForTransparentActivityBasedOnOpaqueBounds() {
+ final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
+ WindowInsets.Type.navigationBars());
+ taskbar.setInsetsRoundedCornerFrame(true);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+ final Rect opaqueBounds = new Rect(0, 0, 500, 300);
+ doReturn(opaqueBounds).when(mActivity).getBounds();
+ // Activity is translucent
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController).hasInheritedLetterboxBehavior();
+
+ // Makes requested sizes different
+ mainWindow.mRequestedWidth = opaqueBounds.width() - 1;
+ mainWindow.mRequestedHeight = opaqueBounds.height() - 1;
+ assertNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow));
+
+ // Makes requested sizes equals
+ mainWindow.mRequestedWidth = opaqueBounds.width();
+ mainWindow.mRequestedHeight = opaqueBounds.height();
+ assertNotNull(mActivity.mLetterboxUiController.getCropBoundsIfNeeded(mainWindow));
+ }
+
+ @Test
public void testGetCropBoundsIfNeeded_noCrop() {
final InsetsSource taskbar = new InsetsSource(/*id=*/ 0,
WindowInsets.Type.navigationBars());
@@ -919,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/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 06bcbf3..2dd34eb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3511,6 +3511,51 @@
}
@Test
+ public void testLetterboxAlignedToBottom_NotOverlappingNavbar() {
+ assertLandscapeActivityAlignedToBottomWithNavbar(false /* immersive */);
+ }
+
+ @Test
+ public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
+ assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
+ }
+
+ private void assertLandscapeActivityAlignedToBottomWithNavbar(boolean immersive) {
+ final int screenHeight = 2800;
+ final int screenWidth = 1400;
+ final int taskbarHeight = 200;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true);
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(1.0f);
+
+ final InsetsSource navSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ navSource.setInsetsRoundedCornerFrame(true);
+ // Immersive activity has transient navbar
+ navSource.setVisible(!immersive);
+ navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
+
+ final WindowState w1 = addWindowToActivity(mActivity);
+ w1.mAboveInsetsState.addSource(navSource);
+
+ // Prepare unresizable landscape activity
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
+ doReturn(immersive).when(displayPolicy).isImmersiveMode();
+
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ LetterboxDetails letterboxDetails = mActivity.mLetterboxUiController.getLetterboxDetails();
+
+ // Letterboxed activity at bottom
+ assertEquals(new Rect(0, 2100, 1400, 2800), mActivity.getBounds());
+ final int expectedHeight = immersive ? screenHeight : screenHeight - taskbarHeight;
+ assertEquals(expectedHeight, letterboxDetails.getLetterboxInnerBounds().bottom);
+ }
+
+ @Test
public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(2800, 1000);
@@ -3938,7 +3983,7 @@
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
- 1.0f /*letterboxVerticalPositionMultiplier*/);
+ 1.0f /*letterboxHorizontalPositionMultiplier*/);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
Rect letterboxNoFold = new Rect(2100, 0, 2800, 1400);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index a98429a..72f2c1d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2539,10 +2539,14 @@
}
@Override
- public void reportChooserSelection(String packageName, int userId, String contentType,
- String[] annotations, String action) {
+ public void reportChooserSelection(@NonNull String packageName, int userId,
+ String contentType, String[] annotations, String action) {
if (packageName == null) {
- Slog.w(TAG, "Event report user selecting a null package");
+ throw new IllegalArgumentException("Package selection must not be null.");
+ }
+ // Verify if this package exists before reporting an event for it.
+ if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
+ Slog.w(TAG, "Event report user selecting an invalid package");
return;
}
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;
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index f9b76f4..9a8c965 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -32,7 +32,6 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.DeviceConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
@@ -262,9 +261,6 @@
*/
public static void showSwitchToManagedProfileDialogIfAppropriate(Context context,
int subId, int callingUid, String callingPackage) {
- if (!isSwitchToManagedProfileDialogFlagEnabled()) {
- return;
- }
final long token = Binder.clearCallingIdentity();
try {
UserHandle callingUserHandle = UserHandle.getUserHandleForUid(callingUid);
@@ -302,11 +298,6 @@
}
}
- public static boolean isSwitchToManagedProfileDialogFlagEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
- "enable_switch_to_managed_profile_dialog", false);
- }
-
private static boolean isUidForeground(Context context, int uid) {
ActivityManager am = context.getSystemService(ActivityManager.class);
boolean result = am != null && am.getUidImportance(uid)