Merge "Add shadow to dream overlay home controls button." 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 19b3141..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;
@@ -44,6 +47,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.compat.CompatChanges;
+import android.app.role.RoleManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
@@ -59,6 +63,7 @@
import android.hardware.usb.UsbManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.permission.PermissionCheckerManager;
import android.provider.DeviceConfig;
import android.text.TextUtils;
@@ -75,6 +80,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Optional;
/**
@@ -265,7 +271,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -281,7 +288,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -299,7 +307,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_DATA_SYNC /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -317,7 +326,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PLAYBACK /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -334,10 +344,12 @@
new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL)
}, true),
new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
- new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS)
+ new RegularPermission(Manifest.permission.MANAGE_OWN_CALLS),
+ new RolePermission(RoleManager.ROLE_DIALER)
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_PHONE_CALL /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -358,7 +370,8 @@
new RegularPermission(Manifest.permission.ACCESS_FINE_LOCATION),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_LOCATION /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -388,7 +401,8 @@
new UsbAccessoryPermission(),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_CONNECTED_DEVICE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -409,7 +423,8 @@
new AppOpPermission(AppOpsManager.OP_PROJECT_MEDIA)
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MEDIA_PROJECTION /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -430,7 +445,8 @@
new RegularPermission(Manifest.permission.SYSTEM_CAMERA),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_CAMERA /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -455,7 +471,8 @@
new RegularPermission(Manifest.permission.RECORD_AUDIO),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_MICROPHONE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ true /* foregroundOnlyPermission */
);
/**
@@ -477,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 */
);
/**
@@ -495,7 +513,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_REMOTE_MESSAGING /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -517,7 +536,8 @@
new AppOpPermission(AppOpsManager.OP_ACTIVATE_VPN),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_SYSTEM_EXEMPTED /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -533,7 +553,8 @@
null /* allOfPermissions */,
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -551,7 +572,8 @@
}, true),
null /* anyOfPermissions */,
null /* permissionEnforcementFlag */,
- false /* permissionEnforcementFlagDefaultValue */
+ false /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -569,7 +591,8 @@
}, true),
null /* anyOfPermissions */,
FGS_TYPE_PERM_ENFORCEMENT_FLAG_SPECIAL_USE /* permissionEnforcementFlag */,
- true /* permissionEnforcementFlagDefaultValue */
+ true /* permissionEnforcementFlagDefaultValue */,
+ false /* foregroundOnlyPermission */
);
/**
@@ -637,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
@@ -678,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.
*
@@ -735,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;
@@ -770,7 +827,8 @@
@Nullable ForegroundServiceTypePermissions allOfPermissions,
@Nullable ForegroundServiceTypePermissions anyOfPermissions,
@Nullable String permissionEnforcementFlag,
- boolean permissionEnforcementFlagDefaultValue) {
+ boolean permissionEnforcementFlagDefaultValue,
+ boolean foregroundOnlyPermission) {
mType = type;
mDeprecationChangeId = deprecationChangeId;
mDisabledChangeId = disabledChangeId;
@@ -779,6 +837,7 @@
mPermissionEnforcementFlag = permissionEnforcementFlag;
mPermissionEnforcementFlagDefaultValue = permissionEnforcementFlagDefaultValue;
mPermissionEnforcementFlagValue = permissionEnforcementFlagDefaultValue;
+ mForegroundOnlyPermission = foregroundOnlyPermission;
}
/**
@@ -881,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.
@@ -1078,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;
+ }
}
}
@@ -1131,6 +1210,29 @@
}
/**
+ * This represents a particular role an app needs to hold for a specific service type.
+ */
+ static class RolePermission extends ForegroundServiceTypePermission {
+ final String mRole;
+
+ RolePermission(@NonNull String role) {
+ super(role);
+ mRole = role;
+ }
+
+ @Override
+ @PackageManager.PermissionResult
+ public int checkPermission(@NonNull Context context, int callerUid, int callerPid,
+ String packageName, boolean allowWhileInUse) {
+ final RoleManager rm = context.getSystemService(RoleManager.class);
+ final List<String> holders = rm.getRoleHoldersAsUser(mRole,
+ UserHandle.getUserHandleForUid(callerUid));
+ return holders != null && holders.contains(packageName)
+ ? PERMISSION_GRANTED : PERMISSION_DENIED;
+ }
+ }
+
+ /**
* This represents a special Android permission to be required for accessing usb devices.
*/
static class UsbDevicePermission extends ForegroundServiceTypePermission {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e15e08f..0ec3847 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,13 +147,13 @@
int checkPermission(in String permission, int pid, int uid);
/** Logs start of an API call to associate with an FGS, used for FGS Type Metrics */
- void logFgsApiBegin(int apiType, int appUid, int appPid);
+ oneway void logFgsApiBegin(int apiType, int appUid, int appPid);
/** Logs stop of an API call to associate with an FGS, used for FGS Type Metrics */
- void logFgsApiEnd(int apiType, int appUid, int appPid);
+ oneway void logFgsApiEnd(int apiType, int appUid, int appPid);
/** Logs API state change to associate with an FGS, used for FGS Type Metrics */
- void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
+ oneway void logFgsApiStateChanged(int apiType, int state, int appUid, int appPid);
// =============== End of transactions used on native side as well ============================
// Special low-level communication with activity manager.
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/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 0512c75..456c6af 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1864,9 +1864,54 @@
public static final String WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON =
PREFIX + "TURN_ON_WORK_PROFILE_BUTTON_TEXT";
+ /**
+ * Information section shown on a dialog when the user is unable to place a call in
+ * the personal profile due to admin restrictions, and must choose whether to place
+ * the call from the work profile or cancel.
+ */
+ public static final String MINIRESOLVER_WORK_TELEPHONY_CALL_BLOCKED_INFORMATION =
+ PREFIX + "MINIRESOLVER_WORK_TELEPHONY_INFORMATION";
+
+ /**
+ * Information section shown on a dialog when the user is unable to send a text in
+ * the personal profile due to admin restrictions, and must choose whether to place
+ * the call from the work profile or cancel.
+ */
+ public static final String MINIRESOLVER_WORK_TELEPHONY_TEXT_BLOCKED_INFORMATION =
+ PREFIX + "MINIRESOLVER_WORK_TELEPHONY_INFORMATION";
+
+
+ /**
+ * Button for a dialog shown when the user is unable to place a call in the personal
+ * profile due to admin restrictions, and must choose whether to place the call from
+ * the work profile or cancel.
+ */
+ public static final String MINIRESOLVER_CALL_FROM_WORK =
+ PREFIX + "MINIRESOLVER_CALL_FROM_WORK";
+
+ /**
+ * Button for a dialog shown when the user has no apps capable of handling an intent
+ * in the personal profile, and must choose whether to open the intent in a
+ * cross-profile app in the work profile, or cancel.
+ */
+ public static final String MINIRESOLVER_SWITCH_TO_WORK =
+ PREFIX + "MINIRESOLVER_SWITCH_TO_WORK";
+
+ /**
+ * Title for a dialog shown when the user has no apps capable of handling an intent
+ * in the personal profile, and must choose whether to open the intent in a
+ * cross-profile app in the work profile, or open in the same profile browser. Accepts
+ * the app name as a param.
+ */
public static final String MINIRESOLVER_OPEN_IN_WORK =
PREFIX + "MINIRESOLVER_OPEN_IN_WORK";
+ /**
+ * Title for a dialog shown when the user has no apps capable of handling an intent
+ * in the personal profile, and must choose whether to open the intent in a
+ * cross-profile app in the personal profile, or open in the same profile browser.
+ * Accepts the app name as a param.
+ */
public static final String MINIRESOLVER_OPEN_IN_PERSONAL =
PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
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 fa99b59..2200af6 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -145,7 +145,7 @@
private boolean mExported;
private boolean mNoPerms;
private boolean mSingleUser;
- private SparseBooleanArray mUsersRedirectedToOwner = new SparseBooleanArray();
+ private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray();
private ThreadLocal<AttributionSource> mCallingAttributionSource;
@@ -874,34 +874,43 @@
return true;
}
- if (isAuthorityRedirectedForCloneProfile(mAuthority)) {
- if (mUsersRedirectedToOwner.indexOfKey(callingUserId) >= 0) {
- return mUsersRedirectedToOwner.get(callingUserId);
+ // Provider user-id will be determined from User Space of the calling app.
+ return isContentRedirectionAllowedForUser(callingUserId);
+ }
+
+ /**
+ * Verify that content redirection is allowed or not.
+ * We check:
+ * 1. Type of Authority
+ * 2. UserProperties allow content sharing
+ *
+ * @param incomingUserId - Provider's user-id to be passed should be based upon:
+ * 1. If client is a cloned app running in user 10, it should be that (10)
+ * 2. If client is accessing content by hinting user space of content,
+ * like sysUi (residing in user 0) accessing 'content://11@media/external'
+ * then it should be 11.
+ */
+ private boolean isContentRedirectionAllowedForUser(int incomingUserId) {
+ if (MediaStore.AUTHORITY.equals(mAuthority)) {
+ int incomingUserIdIndex = mUsersRedirectedToOwnerForMedia.indexOfKey(incomingUserId);
+ if (incomingUserIdIndex >= 0) {
+ return mUsersRedirectedToOwnerForMedia.valueAt(incomingUserIdIndex);
}
// Haven't seen this user yet, look it up
- try {
- UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
- Context callingUserContext = mContext.createPackageContextAsUser("system",
- 0, callingUser);
- UserManager um = callingUserContext.getSystemService(UserManager.class);
-
- if (um != null && um.isCloneProfile()) {
- UserHandle parent = um.getProfileParent(callingUser);
-
- if (parent != null && parent.equals(myUserHandle())) {
- mUsersRedirectedToOwner.put(callingUserId, true);
- return true;
- }
+ UserManager um = mContext.getSystemService(UserManager.class);
+ if (um != null && um.getUserProperties(UserHandle.of(incomingUserId))
+ .isMediaSharedWithParent()) {
+ UserHandle parent = um.getProfileParent(UserHandle.of(incomingUserId));
+ if (parent != null && parent.equals(myUserHandle())) {
+ mUsersRedirectedToOwnerForMedia.put(incomingUserId, true);
+ return true;
}
- } catch (PackageManager.NameNotFoundException e) {
- // ignore
}
- mUsersRedirectedToOwner.put(callingUserId, false);
+ mUsersRedirectedToOwnerForMedia.put(incomingUserId, false);
return false;
}
-
return false;
}
@@ -2734,7 +2743,11 @@
String auth = uri.getAuthority();
if (!mSingleUser) {
int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT);
- if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId()) {
+ if (userId != UserHandle.USER_CURRENT
+ && userId != mContext.getUserId()
+ // Since userId specified in content uri, the provider userId would be
+ // determined from it.
+ && !isContentRedirectionAllowedForUser(userId)) {
throw new SecurityException("trying to query a ContentProvider in user "
+ mContext.getUserId() + " with a uri belonging to user " + userId);
}
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/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7e0954a..be8b2a2 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -168,7 +168,8 @@
* <p>Starting foreground service with this type from apps targeting API level
* {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and later, will require permission
* {@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
- * {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ * {@link android.Manifest.permission#MANAGE_OWN_CALLS} or holding the default
+ * {@link android.app.role.RoleManager#ROLE_DIALER dialer role}.
*/
@RequiresPermission(
allOf = {
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..4f6bcb6 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);
}
@@ -187,37 +185,41 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parseApkLite");
try {
for (File file : files) {
- if (isApkFile(file)) {
- final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
- if (result.isError()) {
- return input.error(result);
- }
+ if (!isApkFile(file)) {
+ continue;
+ }
- final ApkLite lite = result.getResult();
- // Assert that all package names and version codes are
- // consistent with the first one we encounter.
- if (packageName == null) {
- packageName = lite.getPackageName();
- versionCode = lite.getVersionCode();
- } else {
- if (!packageName.equals(lite.getPackageName())) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Inconsistent package " + lite.getPackageName() + " in " + file
- + "; expected " + packageName);
- }
- if (versionCode != lite.getVersionCode()) {
- return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Inconsistent version " + lite.getVersionCode() + " in " + file
- + "; expected " + versionCode);
- }
- }
+ final ParseResult<ApkLite> result = parseApkLite(input, file, flags);
+ if (result.isError()) {
+ return input.error(result);
+ }
- // Assert that each split is defined only oncuses-static-libe
- if (apks.put(lite.getSplitName(), lite) != null) {
+ final ApkLite lite = result.getResult();
+ // Assert that all package names and version codes are
+ // consistent with the first one we encounter.
+ if (packageName == null) {
+ packageName = lite.getPackageName();
+ versionCode = lite.getVersionCode();
+ } else {
+ if (!packageName.equals(lite.getPackageName())) {
return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
- "Split name " + lite.getSplitName()
- + " defined more than once; most recent was " + file);
+ "Inconsistent package " + lite.getPackageName() + " in " + file
+ + "; expected " + packageName);
}
+ if (versionCode != lite.getVersionCode()) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Inconsistent version " + lite.getVersionCode() + " in " + file
+ + "; expected " + versionCode);
+ }
+ }
+
+ // Assert that each split is defined only once
+ ApkLite prev = apks.put(lite.getSplitName(), lite);
+ if (prev != null) {
+ return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
+ "Split name " + lite.getSplitName()
+ + " defined more than once; most recent was " + file
+ + ", previous was " + prev.getPath());
}
}
baseApk = apks.remove(null);
@@ -301,8 +303,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 +430,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 +613,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/biometrics/ComponentInfoInternal.java b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
index 2e708de..3b61a56 100644
--- a/core/java/android/hardware/biometrics/ComponentInfoInternal.java
+++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
@@ -19,8 +19,6 @@
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.IndentingPrintWriter;
/**
* The internal class for storing the component info for a subsystem of the biometric sensor,
@@ -92,19 +90,12 @@
dest.writeString(softwareVersion);
}
- /**
- * Print the component info into the given stream.
- *
- * @param pw The stream to dump the info into.
- * @hide
- */
- public void dump(@NonNull IndentingPrintWriter pw) {
- pw.println(TextUtils.formatSimple("componentId: %s", componentId));
- pw.increaseIndent();
- pw.println(TextUtils.formatSimple("hardwareVersion: %s", hardwareVersion));
- pw.println(TextUtils.formatSimple("firmwareVersion: %s", firmwareVersion));
- pw.println(TextUtils.formatSimple("serialNumber: %s", serialNumber));
- pw.println(TextUtils.formatSimple("softwareVersion: %s", softwareVersion));
- pw.decreaseIndent();
+ @Override
+ public String toString() {
+ return "ComponentId: " + componentId
+ + ", HardwareVersion: " + hardwareVersion
+ + ", FirmwareVersion: " + firmwareVersion
+ + ", SerialNumber " + serialNumber
+ + ", SoftwareVersion: " + softwareVersion;
}
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 1286046..18c8d1b 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -58,10 +58,10 @@
boolean hasEnrolledBiometrics(int userId, String opPackageName);
// Registers an authenticator (e.g. face, fingerprint, iris).
- // Sensor Id in sensor props must be unique, whereas modality doesn't need to be.
+ // Id must be unique, whereas strength and modality don't need to be.
// TODO(b/123321528): Turn strength and modality into enums.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticator(int modality, in SensorPropertiesInternal props,
+ void registerAuthenticator(int id, int modality, int strength,
IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index e908ced..48b5cac 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -212,14 +212,7 @@
@GuardedBy("mLock")
private boolean mFoldedDeviceState;
- private final CameraManager.DeviceStateListener mFoldStateListener =
- new CameraManager.DeviceStateListener() {
- @Override
- public final void onDeviceStateChanged(boolean folded) {
- synchronized (mLock) {
- mFoldedDeviceState = folded;
- }
- }};
+ private CameraManager.DeviceStateListener mFoldStateListener;
private static final String TAG = "CameraCharacteristics";
@@ -245,7 +238,18 @@
/**
* Return the device state listener for this Camera characteristics instance
*/
- CameraManager.DeviceStateListener getDeviceStateListener() { return mFoldStateListener; }
+ CameraManager.DeviceStateListener getDeviceStateListener() {
+ if (mFoldStateListener == null) {
+ mFoldStateListener = new CameraManager.DeviceStateListener() {
+ @Override
+ public final void onDeviceStateChanged(boolean folded) {
+ synchronized (mLock) {
+ mFoldedDeviceState = folded;
+ }
+ }};
+ }
+ return mFoldStateListener;
+ }
/**
* Overrides the property value
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c6fc69e..85f8ca6 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
*/
@@ -1811,6 +1836,7 @@
ctx.getSystemService(DeviceStateManager.class).registerCallback(
new HandlerExecutor(mDeviceStateHandler), mFoldStateListener);
} catch (IllegalStateException e) {
+ mFoldStateListener = null;
Log.v(TAG, "Failed to register device state listener!");
Log.v(TAG, "Device state dependent characteristics updates will not be" +
"functional!");
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f662c73..4cbb040 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6969,10 +6969,10 @@
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
- if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
+ if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_CTRL_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
- KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
+ KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5b6df1c..e95ba79 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1185,6 +1185,68 @@
"android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_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 override that changes the min aspect ratio.
+ *
+ * <p>When this compat override is enabled the min aspect ratio given in the app's manifest can
+ * be overridden by the device manufacturer using their discretion to improve display
+ * compatibility unless the app's manifest value is higher. This treatment will also apply if
+ * no min aspect ratio value is provided in the manifest. These treatments can apply only in
+ * specific cases (e.g. device is in portrait) or each time the app is displayed on screen.
+ *
+ * <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_MIN_ASPECT_RATIO_OVERRIDE"
+ * android:value="true|false"/>
+ * </application>
+ * </pre>
+ * @hide
+ */
+ // TODO(b/279428317): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE =
+ "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/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index c289506..34e6e49 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -783,7 +783,7 @@
}
}
- private class SetEmptyView extends Action {
+ private static class SetEmptyView extends Action {
int emptyViewId;
SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) {
@@ -820,7 +820,7 @@
}
}
- private class SetPendingIntentTemplate extends Action {
+ private static class SetPendingIntentTemplate extends Action {
public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) {
this.viewId = id;
this.pendingIntentTemplate = pendingIntentTemplate;
@@ -891,7 +891,7 @@
PendingIntent pendingIntentTemplate;
}
- private class SetRemoteViewsAdapterList extends Action {
+ private static class SetRemoteViewsAdapterList extends Action {
public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list,
int viewTypeCount) {
this.viewId = id;
@@ -1354,7 +1354,7 @@
}
@Nullable
- private MethodHandle getMethod(View view, String methodName, Class<?> paramType,
+ private static MethodHandle getMethod(View view, String methodName, Class<?> paramType,
boolean async) {
MethodArgs result;
Class<? extends View> klass = view.getClass();
@@ -1433,7 +1433,7 @@
* to {@link ImageView#getDrawable()}.
* <p>
*/
- private class SetDrawableTint extends Action {
+ private static class SetDrawableTint extends Action {
SetDrawableTint(@IdRes int id, boolean targetBackground,
@ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
this.viewId = id;
@@ -1697,7 +1697,7 @@
/**
* Base class for the reflection actions.
*/
- private abstract class BaseReflectionAction extends Action {
+ private abstract static class BaseReflectionAction extends Action {
static final int BOOLEAN = 1;
static final int BYTE = 2;
static final int SHORT = 3;
@@ -1860,7 +1860,7 @@
}
/** Class for the reflection actions. */
- private final class ReflectionAction extends BaseReflectionAction {
+ private static final class ReflectionAction extends BaseReflectionAction {
@UnsupportedAppUsage
Object value;
@@ -2006,7 +2006,7 @@
}
}
- private final class ResourceReflectionAction extends BaseReflectionAction {
+ private static final class ResourceReflectionAction extends BaseReflectionAction {
static final int DIMEN_RESOURCE = 1;
static final int COLOR_RESOURCE = 2;
@@ -2093,7 +2093,7 @@
}
}
- private final class AttributeReflectionAction extends BaseReflectionAction {
+ private static final class AttributeReflectionAction extends BaseReflectionAction {
static final int DIMEN_RESOURCE = 1;
static final int COLOR_RESOURCE = 2;
@@ -2187,7 +2187,7 @@
return ATTRIBUTE_REFLECTION_ACTION_TAG;
}
}
- private final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction {
+ private static final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction {
private final float mValue;
@ComplexDimensionUnit
@@ -2243,7 +2243,7 @@
}
}
- private final class NightModeReflectionAction extends BaseReflectionAction {
+ private static final class NightModeReflectionAction extends BaseReflectionAction {
private final Object mLightValue;
private final Object mDarkValue;
@@ -2603,7 +2603,7 @@
/**
* ViewGroup methods related to removing child views.
*/
- private class ViewGroupActionRemove extends Action {
+ private static class ViewGroupActionRemove extends Action {
/**
* Id that indicates that all child views of the affected ViewGroup should be removed.
*
@@ -2725,7 +2725,7 @@
/**
* Action to remove a view from its parent.
*/
- private class RemoveFromParentAction extends Action {
+ private static class RemoveFromParentAction extends Action {
RemoveFromParentAction(@IdRes int viewId) {
this.viewId = viewId;
@@ -2795,7 +2795,7 @@
* Helper action to set compound drawables on a TextView. Supports relative
* (s/t/e/b) or cardinal (l/t/r/b) arrangement.
*/
- private class TextViewDrawableAction extends Action {
+ private static class TextViewDrawableAction extends Action {
public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1,
@DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) {
this.viewId = viewId;
@@ -2942,7 +2942,7 @@
/**
* Helper action to set text size on a TextView in any supported units.
*/
- private class TextViewSizeAction extends Action {
+ private static class TextViewSizeAction extends Action {
TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) {
this.viewId = viewId;
this.units = units;
@@ -2980,7 +2980,7 @@
/**
* Helper action to set padding on a View.
*/
- private class ViewPaddingAction extends Action {
+ private static class ViewPaddingAction extends Action {
public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top,
@Px int right, @Px int bottom) {
this.viewId = viewId;
@@ -3210,7 +3210,7 @@
/**
* Helper action to add a view tag with RemoteInputs.
*/
- private class SetRemoteInputsAction extends Action {
+ private static class SetRemoteInputsAction extends Action {
public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) {
this.viewId = viewId;
@@ -3246,7 +3246,7 @@
/**
* Helper action to override all textViewColors
*/
- private class OverrideTextColorsAction extends Action {
+ private static class OverrideTextColorsAction extends Action {
private final int textColor;
@@ -3289,7 +3289,7 @@
}
}
- private class SetIntTagAction extends Action {
+ private static class SetIntTagAction extends Action {
@IdRes private final int mViewId;
@IdRes private final int mKey;
private final int mTag;
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 904fb66..7452daa 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -19,7 +19,11 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_CALL_FROM_WORK;
import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_SWITCH_TO_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_WORK_TELEPHONY_CALL_BLOCKED_INFORMATION;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_WORK_TELEPHONY_TEXT_BLOCKED_INFORMATION;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -29,9 +33,11 @@
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;
+import android.app.admin.ManagedSubscriptionsPolicy;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -41,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
import android.metrics.LogMaker;
import android.os.Build;
import android.os.Bundle;
@@ -48,6 +55,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.telecom.TelecomManager;
import android.util.Slog;
import android.view.View;
import android.widget.Button;
@@ -203,35 +211,124 @@
findViewById(R.id.title_container).setElevation(0);
- ImageView icon = findViewById(R.id.icon);
PackageManager packageManagerForTargetUser =
createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
.getPackageManager();
- icon.setImageDrawable(target.loadIcon(packageManagerForTargetUser));
+
+ ImageView icon = findViewById(R.id.icon);
+ icon.setImageDrawable(
+ getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
View buttonContainer = findViewById(R.id.button_bar_container);
buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
((TextView) findViewById(R.id.open_cross_profile)).setText(
- getOpenInWorkMessage(target.loadLabel(packageManagerForTargetUser)));
+ getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
// The mini-resolver's negative button is reused in this flow to cancel the intent
((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
findViewById(R.id.use_same_profile_browser).setOnClickListener(v -> finish());
+ ((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();
});
+
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+ DevicePolicyManager devicePolicyManager =
+ getSystemService(DevicePolicyManager.class);
+ // Additional information section is work telephony specific. Therefore, it is only shown
+ // for telephony related intents, when all sim subscriptions are in the work profile.
+ if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+ && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ telephonyInfo.setVisibility(View.VISIBLE);
+ ((TextView) findViewById(R.id.miniresolver_info_section_text))
+ .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+ } else {
+ telephonyInfo.setVisibility(View.GONE);
+ }
}
- private String getOpenInWorkMessage(CharSequence targetLabel) {
+ private Drawable getAppIcon(
+ ResolveInfo target,
+ Intent launchIntent,
+ int targetUserId,
+ PackageManager packageManagerForTargetUser) {
+ if (isDialerIntent(launchIntent)) {
+ // The icon for the call intent will be a generic phone icon as the target will be
+ // the telecom call handler. From the user's perspective, they are being directed
+ // to the dialer app, so use the icon from that app instead.
+ TelecomManager telecomManager =
+ getApplicationContext().getSystemService(TelecomManager.class);
+ String defaultDialerPackageName =
+ telecomManager.getDefaultDialerPackage(UserHandle.of(targetUserId));
+ try {
+ return packageManagerForTargetUser
+ .getApplicationInfo(defaultDialerPackageName, /* flags= */ 0)
+ .loadIcon(packageManagerForTargetUser);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Allow to fall-through to the icon from the target if we can't find the default
+ // dialer icon.
+ Slog.w(TAG, "Cannot load icon for default dialer package");
+ }
+ }
+ return target.loadIcon(packageManagerForTargetUser);
+ }
+
+ private int getOpenInWorkButtonString(Intent launchIntent) {
+ if (isDialerIntent(launchIntent)) {
+ return R.string.miniresolver_call;
+ }
+ if (isTextMessageIntent(launchIntent)) {
+ return R.string.miniresolver_switch;
+ }
+ return R.string.whichViewApplicationLabel;
+ }
+
+ private String getOpenInWorkMessage(Intent launchIntent, CharSequence targetLabel) {
+ if (isDialerIntent(launchIntent)) {
+ return getSystemService(DevicePolicyManager.class).getResources().getString(
+ MINIRESOLVER_CALL_FROM_WORK,
+ () -> getString(R.string.miniresolver_call_in_work));
+ }
+ if (isTextMessageIntent(launchIntent)) {
+ return getSystemService(DevicePolicyManager.class).getResources().getString(
+ MINIRESOLVER_SWITCH_TO_WORK,
+ () -> getString(R.string.miniresolver_switch_to_work));
+ }
return getSystemService(DevicePolicyManager.class).getResources().getString(
MINIRESOLVER_OPEN_WORK,
() -> getString(R.string.miniresolver_open_work, targetLabel),
targetLabel);
}
+ private String getWorkTelephonyInfoSectionMessage(Intent launchIntent) {
+ if (isDialerIntent(launchIntent)) {
+ return getSystemService(DevicePolicyManager.class).getResources().getString(
+ MINIRESOLVER_WORK_TELEPHONY_CALL_BLOCKED_INFORMATION,
+ () -> getString(R.string.miniresolver_call_information));
+ }
+ if (isTextMessageIntent(launchIntent)) {
+ return getSystemService(DevicePolicyManager.class).getResources().getString(
+ MINIRESOLVER_WORK_TELEPHONY_TEXT_BLOCKED_INFORMATION,
+ () -> getString(R.string.miniresolver_sms_information));
+ }
+ return "";
+ }
+
+
+
private String getForwardToPersonalMessage() {
return getSystemService(DevicePolicyManager.class).getResources().getString(
FORWARD_INTENT_TO_PERSONAL,
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/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml
index 1ad3acd..db0ea54 100644
--- a/core/res/res/layout/miniresolver.xml
+++ b/core/res/res/layout/miniresolver.xml
@@ -63,6 +63,37 @@
android:textColor="?android:textColorPrimary"
/>
</RelativeLayout>
+ <!-- Additional information section, currently only shown when redirecting to Telephony apps -->
+ <LinearLayout
+ android:id="@+id/miniresolver_info_section"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ android:paddingBottom="48dp"
+ android:visibility="gone"
+ android:background="?attr/colorBackground"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/miniresolver_info_section_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:src="@drawable/ic_info_outline_24"
+ android:tint="?android:textColorSecondary"
+ />
+
+ <TextView
+ android:id="@+id/miniresolver_info_section_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:paddingEnd="8dp"
+ android:textSize="14sp"
+ android:textAllCaps="false"
+ android:textColor="?android:textColorSecondary"
+ />
+ </LinearLayout>
<LinearLayout
android:id="@+id/button_bar_container"
@@ -83,7 +114,7 @@
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
- android:paddingHorizontal="16dp"
+ android:layout_marginHorizontal="24dp"
android:paddingBottom="2dp"
android:elevation="@dimen/resolver_elevation">
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 1bbe8ee..b7d088b 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1618,7 +1618,8 @@
{@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, starting a foreground
service with this type will require permission
{@link android.Manifest.permission#FOREGROUND_SERVICE_PHONE_CALL} and
- {@link android.Manifest.permission#MANAGE_OWN_CALLS}.
+ {@link android.Manifest.permission#MANAGE_OWN_CALLS} or holding the default
+ {@link android.app.role.RoleManager#ROLE_DIALER dialer role}.
-->
<flag name="phoneCall" value="0x04" />
<!-- GPS, map, navigation location update.
@@ -1796,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
@@ -1841,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/strings.xml b/core/res/res/values/strings.xml
index d480748..91fbf6b 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -861,10 +861,10 @@
<!-- "Switch" is a verb; it means to change user profile by tapping another user profile name. -->
<string name="managed_profile_label">Switch to work profile</string>
- <!-- "Switch" is a verb; it means to change user profile by tapping another user profile name. -->
+ <!-- "Switch" is a verb; it means to change user profile by switching to an app in the personal profile. -->
<string name="user_owner_app_label">Switch to personal <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
- <!-- "Switch" is a verb; it means to change user profile by tapping another user profile name. -->
+ <!-- "Switch" is a verb; it means to change user profile by switching to an app in the work profile. -->
<string name="managed_profile_app_label">Switch to work <xliff:g id="app_name" example="Gmail">%1$s</xliff:g></string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
@@ -5907,10 +5907,24 @@
<string name="miniresolver_open_in_personal">Open in personal <xliff:g id="app" example="YouTube">%s</xliff:g>?</string>
<!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_open_in_work">Open in work <xliff:g id="app" example="YouTube">%s</xliff:g>?</string>
+ <!-- Dialog title. User must place the phone call in the other profile, or cancel. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_call_in_work">Call from work app?</string>
+ <!-- Dialog title. User much choose between opening content in a cross-profile app or cancelling. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_switch_to_work">Switch to work app?</string>
+ <!-- Dialog text. Shown when the user is unable to make a phone call from a personal app due to restrictions set
+ by their organization, and so must switch to a work app or cancel. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_call_information">Your organization only allows you to make calls from work apps</string>
+ <!-- Dialog text. Shown when the user is unable to send a text message from a personal app due to restrictions set
+ by their organization, and so must switch to a work app or cancel. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_sms_information">Your organization only allows you to send messages from work apps</string>
<!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_use_personal_browser">Use personal browser</string>
<!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
<string name="miniresolver_use_work_browser">Use work browser</string>
+ <!-- Button option. Action to place a phone call. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_call">Call</string>
+ <!-- Button option. Action to open an app in the work profile. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_switch">Switch</string>
<!-- Icc depersonalization related strings -->
<!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e08f085..f35e32b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1577,6 +1577,14 @@
<java-symbol type="string" name="miniresolver_open_work" />
<java-symbol type="string" name="miniresolver_use_personal_browser" />
<java-symbol type="string" name="miniresolver_use_work_browser" />
+ <java-symbol type="string" name="miniresolver_call_in_work" />
+ <java-symbol type="string" name="miniresolver_switch_to_work" />
+ <java-symbol type="string" name="miniresolver_call" />
+ <java-symbol type="string" name="miniresolver_switch" />
+ <java-symbol type="string" name="miniresolver_call_information" />
+ <java-symbol type="string" name="miniresolver_sms_information" />
+ <java-symbol type="id" name="miniresolver_info_section" />
+ <java-symbol type="id" name="miniresolver_info_section_text" />
<java-symbol type="id" name="button_open" />
<java-symbol type="id" name="use_same_profile_browser" />
@@ -3652,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" />
@@ -4528,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 -->
@@ -5009,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/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 55680ab..9595332 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -229,20 +229,36 @@
@Test
public void testGetIndex() {
- for (int index = 0; index < 2048; index++) {
- for (int type = FIRST; type <= LAST; type = type << 1) {
- final int id = InsetsSource.createId(null, index, type);
- assertEquals(index, InsetsSource.getIndex(id));
+ // Here doesn't iterate all the owners, or the test cannot be done before timeout.
+ for (int owner = 0; owner < 100; owner++) {
+ for (int index = 0; index < 2048; index++) {
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ final int id = InsetsSource.createId(owner, index, type);
+ final int indexFromId = InsetsSource.getIndex(id);
+ assertEquals("index and indexFromId must be the same. id=" + id
+ + ", owner=" + owner
+ + ", index=" + index
+ + ", type=" + type
+ + ", indexFromId=" + indexFromId + ".", index, indexFromId);
+ }
}
}
}
@Test
public void testGetType() {
- for (int index = 0; index < 2048; index++) {
- for (int type = FIRST; type <= LAST; type = type << 1) {
- final int id = InsetsSource.createId(null, index, type);
- assertEquals(type, InsetsSource.getType(id));
+ // Here doesn't iterate all the owners, or the test cannot be done before timeout.
+ for (int owner = 0; owner < 100; owner++) {
+ for (int index = 0; index < 2048; index++) {
+ for (int type = FIRST; type <= LAST; type = type << 1) {
+ final int id = InsetsSource.createId(owner, index, type);
+ final int typeFromId = InsetsSource.getType(id);
+ assertEquals("type and typeFromId must be the same. id=" + id
+ + ", owner=" + owner
+ + ", index=" + index
+ + ", type=" + type
+ + ", typeFromId=" + typeFromId + ".", type, typeFromId);
+ }
}
}
}
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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index ead5fd4..a044602 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -525,6 +525,8 @@
<permission name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
<!-- Permission required for GTS test - GtsCredentialsTestCases -->
<permission name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
+ <!-- Permission required for CTS test IntentRedirectionTest -->
+ <permission name="android.permission.QUERY_CLONED_APPS"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 92c0dab..2a0e476 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2059,6 +2059,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-233530384": {
+ "message": "Content Recording: Incoming session on display %d can't be set since it is already null; the corresponding VirtualDisplay must have already been removed.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecordingController.java"
+ },
"-230587670": {
"message": "SyncGroup %d: Unfinished container: %s",
"level": "VERBOSE",
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 658d92c..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));
}
@@ -342,11 +379,22 @@
mRearDisplayPresentationController);
DeviceStateRequest concurrentDisplayStateRequest = DeviceStateRequest.newBuilder(
mConcurrentDisplayState).build();
- mDeviceStateManager.requestState(
- concurrentDisplayStateRequest,
- mExecutor,
- deviceStateCallback
- );
+
+ try {
+ mDeviceStateManager.requestState(
+ concurrentDisplayStateRequest,
+ mExecutor,
+ deviceStateCallback
+ );
+ } catch (SecurityException e) {
+ // If a SecurityException occurs when invoking DeviceStateManager#requestState
+ // (e.g. if the caller is not in the foreground, or if it does not have the required
+ // permissions), we should first clean up our local state before re-throwing the
+ // SecurityException to the caller. Otherwise, subsequent attempts to
+ // startRearDisplayPresentationSession will always fail.
+ mRearDisplayPresentationController = null;
+ throw e;
+ }
}
}
@@ -480,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/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 59f120d..4d87c95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -61,6 +61,9 @@
@VisibleForTesting
final ActivityEmbeddingAnimationSpec mAnimationSpec;
+ @Nullable
+ private Animator mActiveAnimator;
+
ActivityEmbeddingAnimationRunner(@NonNull Context context,
@NonNull ActivityEmbeddingController controller) {
mController = controller;
@@ -75,8 +78,10 @@
// applied to make sure the surface is ready.
final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
new ArrayList<>();
- final Animator animator = createAnimator(info, startTransaction, finishTransaction,
+ final Animator animator = createAnimator(info, startTransaction,
+ finishTransaction,
() -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
+ mActiveAnimator = animator;
// Start the animation.
if (!postStartTransactionCallbacks.isEmpty()) {
@@ -98,6 +103,17 @@
}
}
+ void cancelAnimationFromMerge() {
+ if (mActiveAnimator == null) {
+ Log.e(TAG,
+ "No active ActivityEmbedding animator running but mergeAnimation is "
+ + "trying to cancel one."
+ );
+ return;
+ }
+ mActiveAnimator.end();
+ }
+
/**
* Sets transition animation scale settings value.
* @param scale The setting value of transition animation scale.
@@ -153,6 +169,7 @@
adapter.onAnimationEnd(t);
}
t.apply();
+ mActiveAnimator = null;
animationFinishCallback.run();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index bfbddbb..fbdbd3e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -118,6 +118,13 @@
return true;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ mAnimationRunner.cancelAnimationFromMerge();
+ }
+
private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
final Rect nonClosingEmbeddedArea = new Rect();
for (int i = changes.size() - 1; i >= 0; i--) {
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/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index a50d357..8e9fc11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1784,10 +1784,13 @@
// We're expanded while the last bubble is being removed. Let the scrim animate away
// and then remove our views (removing the icon view triggers the removal of the
// bubble window so do that at the end of the animation so we see the scrim animate).
+ BadgedImageView iconView = bubble.getIconView();
showScrim(false, () -> {
mRemovingLastBubbleWhileExpanded = false;
bubble.cleanupExpandedView();
- mBubbleContainer.removeView(bubble.getIconView());
+ if (iconView != null) {
+ mBubbleContainer.removeView(iconView);
+ }
bubble.cleanupViews(); // cleans up the icon view
updateExpandedView(); // resets state for no expanded bubble
});
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/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 28368ef..74ef57e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -77,6 +77,8 @@
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -562,6 +564,28 @@
}
//
+ // Keyguard transitions (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static KeyguardTransitionHandler provideKeyguardTransitionHandler(
+ ShellInit shellInit,
+ Transitions transitions,
+ @ShellMainThread Handler mainHandler,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new KeyguardTransitionHandler(
+ shellInit, transitions, mainHandler, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
+ static KeyguardTransitions provideKeyguardTransitions(
+ KeyguardTransitionHandler handler) {
+ return handler.asKeyguardTransitions();
+ }
+
+ //
// Display areas
//
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 f3130d3..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
@@ -60,6 +60,7 @@
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -200,6 +201,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
+ Transitions transitions,
Optional<DesktopModeController> desktopModeController,
Optional<DesktopTasksController> desktopTasksController,
Optional<SplitScreenController> splitScreenController) {
@@ -211,6 +213,7 @@
taskOrganizer,
displayController,
syncQueue,
+ transitions,
desktopModeController,
desktopTasksController,
splitScreenController);
@@ -532,9 +535,10 @@
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
Optional<RecentsTransitionHandler> recentsTransitionHandler,
+ KeyguardTransitionHandler keyguardTransitionHandler,
Transitions transitions) {
return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional,
- pipTouchHandlerOptional, recentsTransitionHandler);
+ pipTouchHandlerOptional, recentsTransitionHandler, keyguardTransitionHandler);
}
@WMSingleton
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/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
new file mode 100644
index 0000000..4d8075a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -0,0 +1,274 @@
+/*
+ * 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.wm.shell.keyguard;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
+
+import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.util.TransitionUtil.isClosingType;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
+
+import java.util.Map;
+
+/**
+ * The handler for Keyguard enter/exit and occlude/unocclude animations.
+ *
+ * <p>This takes the highest priority.
+ */
+public class KeyguardTransitionHandler implements Transitions.TransitionHandler {
+ private static final String TAG = "KeyguardTransition";
+
+ private final Transitions mTransitions;
+ private final Handler mMainHandler;
+ private final ShellExecutor mMainExecutor;
+
+ private final Map<IBinder, IRemoteTransition> mStartedTransitions = new ArrayMap<>();
+
+ /**
+ * Local IRemoteTransition implementations registered by the keyguard service.
+ * @see KeyguardTransitions
+ */
+ private IRemoteTransition mExitTransition = null;
+ private IRemoteTransition mOccludeTransition = null;
+ private IRemoteTransition mOccludeByDreamTransition = null;
+ private IRemoteTransition mUnoccludeTransition = null;
+
+ public KeyguardTransitionHandler(
+ @NonNull ShellInit shellInit,
+ @NonNull Transitions transitions,
+ @NonNull Handler mainHandler,
+ @NonNull ShellExecutor mainExecutor) {
+ mTransitions = transitions;
+ mMainHandler = mainHandler;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mTransitions.addHandler(this);
+ }
+
+ /**
+ * Interface for SystemUI implementations to set custom Keyguard exit/occlude handlers.
+ */
+ @ExternalThread
+ public KeyguardTransitions asKeyguardTransitions() {
+ return new KeyguardTransitionsImpl();
+ }
+
+ public static boolean handles(TransitionInfo info) {
+ return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0
+ || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0
+ || info.getType() == TRANSIT_KEYGUARD_OCCLUDE
+ || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull TransitionFinishCallback finishCallback) {
+ if (!handles(info)) {
+ return false;
+ }
+
+ boolean hasOpeningOcclude = false;
+ boolean hasOpeningDream = false;
+ boolean hasClosingApp = false;
+
+ // Check for occluding/dream/closing apps
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isOpeningType(change.getMode())) {
+ if (change.hasFlags(FLAG_OCCLUDES_KEYGUARD)) {
+ hasOpeningOcclude = true;
+ }
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) {
+ hasOpeningDream = true;
+ }
+ } else if (isClosingType(change.getMode())) {
+ hasClosingApp = true;
+ }
+ }
+
+ // Choose a transition applicable for the changes and keyguard state.
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ return startAnimation(mExitTransition,
+ "going-away",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+ if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) {
+ if (hasOpeningDream) {
+ return startAnimation(mOccludeByDreamTransition,
+ "occlude-by-dream",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ } else {
+ return startAnimation(mOccludeTransition,
+ "occlude",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ }
+ } else if (hasClosingApp || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+ return startAnimation(mUnoccludeTransition,
+ "unocclude",
+ transition, info, startTransaction, finishTransaction, finishCallback);
+ } else {
+ Log.wtf(TAG, "Failed to play: " + info);
+ return false;
+ }
+ }
+
+ private boolean startAnimation(IRemoteTransition remoteHandler, String description,
+ @NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "start keyguard %s transition, info = %s", description, info);
+
+ try {
+ remoteHandler.startAnimation(transition, info, startTransaction,
+ new IRemoteTransitionFinishedCallback.Stub() {
+ @Override
+ public void onTransitionFinished(
+ WindowContainerTransaction wct, SurfaceControl.Transaction sct) {
+ mMainExecutor.execute(() -> {
+ finishCallback.onTransitionFinished(wct, null);
+ });
+ }
+ });
+ mStartedTransitions.put(transition, remoteHandler);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e);
+ return false;
+ }
+ startTransaction.clear();
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
+ @NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
+ @NonNull TransitionFinishCallback nextFinishCallback) {
+ final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
+
+ if (playing == null) {
+ ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "unknown keyguard transition %s", currentTransition);
+ return;
+ }
+
+ if (nextInfo.getType() == TRANSIT_SLEEP) {
+ // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
+ // token is held. In cases where keyguard is showing, we are running the animation for
+ // the device sleeping/waking, so it's best to ignore this and keep playing anyway.
+ return;
+ } else {
+ finishAnimationImmediately(currentTransition);
+ }
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted,
+ SurfaceControl.Transaction finishTransaction) {
+ finishAnimationImmediately(transition);
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ private void finishAnimationImmediately(IBinder transition) {
+ final IRemoteTransition playing = mStartedTransitions.get(transition);
+
+ if (playing != null) {
+ final IBinder fakeTransition = new Binder();
+ final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
+ final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
+ final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
+ try {
+ playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+ } catch (RemoteException e) {
+ // There is no good reason for this to happen because the player is a local object
+ // implementing an AIDL interface.
+ Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
+ }
+ }
+ }
+
+ private static class FakeFinishCallback extends IRemoteTransitionFinishedCallback.Stub {
+ @Override
+ public void onTransitionFinished(
+ WindowContainerTransaction wct, SurfaceControl.Transaction t) {
+ return;
+ }
+ }
+
+ @ExternalThread
+ private final class KeyguardTransitionsImpl implements KeyguardTransitions {
+ @Override
+ public void register(
+ IRemoteTransition exitTransition,
+ IRemoteTransition occludeTransition,
+ IRemoteTransition occludeByDreamTransition,
+ IRemoteTransition unoccludeTransition) {
+ mMainExecutor.execute(() -> {
+ mExitTransition = exitTransition;
+ mOccludeTransition = occludeTransition;
+ mOccludeByDreamTransition = occludeByDreamTransition;
+ mUnoccludeTransition = unoccludeTransition;
+ });
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
new file mode 100644
index 0000000..b4b327f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.shell.keyguard;
+
+import android.annotation.NonNull;
+import android.window.IRemoteTransition;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+/**
+ * Interface exposed to SystemUI Keyguard to register handlers for running
+ * animations on keyguard visibility changes.
+ *
+ * TODO(b/274954192): Merge the occludeTransition and occludeByDream handlers and just let the
+ * keyguard handler make the decision on which version it wants to play.
+ */
+@ExternalThread
+public interface KeyguardTransitions {
+ /**
+ * Registers a set of remote transitions for Keyguard.
+ */
+ default void register(
+ @NonNull IRemoteTransition unlockTransition,
+ @NonNull IRemoteTransition occludeTransition,
+ @NonNull IRemoteTransition occludeByDreamTransition,
+ @NonNull IRemoteTransition unoccludeTransition) {}
+}
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/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 6fa1861..42633b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,6 +40,7 @@
import android.window.WindowContainerTransactionCallback;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -63,6 +64,7 @@
private PipTransitionController mPipHandler;
private RecentsTransitionHandler mRecentsHandler;
private StageCoordinator mSplitHandler;
+ private final KeyguardTransitionHandler mKeyguardHandler;
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
@@ -76,6 +78,9 @@
/** Recents transition while split-screen foreground. */
static final int TYPE_RECENTS_DURING_SPLIT = 4;
+ /** Keyguard exit/occlude/unocclude transition. */
+ static final int TYPE_KEYGUARD = 5;
+
/** The default animation for this mixed transition. */
static final int ANIM_TYPE_DEFAULT = 0;
@@ -126,8 +131,10 @@
public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
- Optional<RecentsTransitionHandler> recentsHandlerOptional) {
+ Optional<RecentsTransitionHandler> recentsHandlerOptional,
+ KeyguardTransitionHandler keyguardHandler) {
mPlayer = player;
+ mKeyguardHandler = keyguardHandler;
if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent()
&& splitScreenControllerOptional.isPresent()) {
// Add after dependencies because it is higher priority
@@ -263,12 +270,26 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+
MixedTransition mixed = null;
for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
if (mActiveTransitions.get(i).mTransition != transition) continue;
mixed = mActiveTransitions.get(i);
break;
}
+
+ // Offer Keyguard the opportunity to take over lock transitions - ideally we could know by
+ // the time of handleRequest, but we need more information than is available at that time.
+ if (KeyguardTransitionHandler.handles(info)) {
+ if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Converting mixed transition into a keyguard transition");
+ onTransitionConsumed(transition, false, null);
+ }
+ mixed = new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ mActiveTransitions.add(mixed);
+ }
+
if (mixed == null) return false;
if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
@@ -282,6 +303,9 @@
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ return mKeyguardHandler.startAnimation(
+ transition, info, startTransaction, finishTransaction, finishCallback);
} else {
mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
@@ -574,6 +598,8 @@
}
mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
finishCallback);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ mKeyguardHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
@@ -597,6 +623,8 @@
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
+ mKeyguardHandler.onTransitionConsumed(transition, aborted, finishT);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 21dca95..6a2468a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -435,6 +435,23 @@
backgroundColorForTransition =
uiContext.getColor(R.color.overview_background);
}
+ if (wallpaperTransit == WALLPAPER_TRANSITION_OPEN
+ && TransitionUtil.isOpeningType(info.getType())) {
+ // Need to flip the z-order of opening/closing because the WALLPAPER_OPEN
+ // always animates the closing task over the opening one while
+ // traditionally, an OPEN transition animates the opening over the closing.
+
+ // See Transitions#setupAnimHierarchy for details about these variables.
+ final int numChanges = info.getChanges().size();
+ final int zSplitLine = numChanges + 1;
+ if (TransitionUtil.isOpeningType(mode)) {
+ final int layer = zSplitLine - i;
+ startTransaction.setLayer(change.getLeash(), layer);
+ } else if (TransitionUtil.isClosingType(mode)) {
+ final int layer = zSplitLine + numChanges - i;
+ startTransaction.setLayer(change.getLeash(), layer);
+ }
+ }
}
final float cornerRadius;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ef2a511..a242c72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -94,8 +94,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)
- && !TransitionUtil.alwaysReportToKeyguard(info)) {
+ if (!Transitions.SHELL_TRANSITIONS_ROTATION && TransitionUtil.hasDisplayChange(info)) {
// Note that if the remote doesn't have permission ACCESS_SURFACE_FLINGER, some
// operations of the start transaction may be ignored.
mRequestedRemotes.remove(transition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index ab27c55..f33b077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -71,6 +71,7 @@
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -686,7 +687,11 @@
active.mToken, info, active.mStartT, active.mFinishT);
}
- if (info.getRootCount() == 0 && !TransitionUtil.alwaysReportToKeyguard(info)) {
+ /*
+ * Some transitions we always need to report to keyguard even if they are empty.
+ * TODO (b/274954192): Remove this once keyguard dispatching fully moves to Shell.
+ */
+ if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
// No root-leashes implies that the transition is empty/no-op, so just do
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
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 402b0ce..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
@@ -85,23 +85,6 @@
return false;
}
- /**
- * Some transitions we always need to report to keyguard even if they are empty.
- * TODO (b/274954192): Remove this once keyguard dispatching moves to Shell.
- */
- public static boolean alwaysReportToKeyguard(TransitionInfo info) {
- // occlusion status of activities can change while screen is off so there will be no
- // visibility change but we still need keyguardservice to be notified.
- if (info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) return true;
-
- // It's possible for some activities to stop with bad timing (esp. since we can't yet
- // queue activity transitions initiated by apps) that results in an empty transition for
- // keyguard going-away. In general, we should should always report Keyguard-going-away.
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) return true;
-
- return false;
- }
-
/** Returns `true` if `change` is a wallpaper. */
public static boolean isWallpaper(TransitionInfo.Change change) {
return (change.getTaskInfo() == null)
@@ -252,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);
}
/**
@@ -264,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);
}
@@ -293,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/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index a625346..4fca8b4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -65,12 +65,14 @@
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
.addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY))
.build();
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(),
+ any());
mAnimRunner.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction);
final ArgumentCaptor<Runnable> finishCallback = ArgumentCaptor.forClass(Runnable.class);
- verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction), eq(mFinishTransaction),
+ verify(mAnimRunner).createAnimator(eq(info), eq(mStartTransaction),
+ eq(mFinishTransaction),
finishCallback.capture(), any());
verify(mStartTransaction).apply();
verify(mAnimator).start();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index 4f4f356..ab1ccd4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -47,6 +47,7 @@
@Mock
ShellInit mShellInit;
+
@Mock
Transitions mTransitions;
@Mock
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index b8f615a..ba34f1f7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -29,9 +29,13 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.graphics.Rect;
+import android.view.SurfaceControl;
import android.window.TransitionInfo;
+import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -58,7 +62,8 @@
@Before
public void setup() {
super.setUp();
- doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+ doReturn(mAnimator).when(mAnimRunner).createAnimator(any(), any(), any(), any(),
+ any());
}
@Test
@@ -182,6 +187,44 @@
verifyNoMoreInteractions(mFinishTransaction);
}
+ @UiThreadTest
+ @Test
+ public void testMergeAnimation() {
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
+ .build();
+
+ final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mController.onAnimationFinished(mTransition);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+ });
+ doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
+ mController.startAnimation(mTransition, info, mStartTransaction,
+ mFinishTransaction, mFinishCallback);
+ verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+ mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
+ mTransition,
+ (wct, cb) -> {
+ });
+ verify(mFinishCallback).onTransitionFinished(any(), any());
+ }
+
@Test
public void testOnAnimationFinished() {
// Should not call finish when there is no transition.
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/AudioManager.java b/media/java/android/media/AudioManager.java
index b1d2e33..4759689 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3730,7 +3730,12 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
public void setA2dpSuspended(boolean enable) {
- AudioSystem.setParameters("A2dpSuspended=" + enable);
+ final IAudioService service = getService();
+ try {
+ service.setA2dpSuspended(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -3743,7 +3748,12 @@
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.BLUETOOTH_STACK)
public void setLeAudioSuspended(boolean enable) {
- AudioSystem.setParameters("LeAudioSuspended=" + enable);
+ final IAudioService service = getService();
+ try {
+ service.setLeAudioSuspended(enable);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index fe5afc5..7ce189b 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -231,6 +231,12 @@
void setBluetoothScoOn(boolean on);
+ @EnforcePermission("BLUETOOTH_STACK")
+ void setA2dpSuspended(boolean on);
+
+ @EnforcePermission("BLUETOOTH_STACK")
+ void setLeAudioSuspended(boolean enable);
+
boolean isBluetoothScoOn();
void setBluetoothA2dpOn(boolean on);
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/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 6f67d68..1b04f18 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -372,13 +372,12 @@
void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) {
ALOGV("LnbClientCallbackImpl::onEvent, type=%d", lnbEventType);
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject lnb(env->NewLocalRef(mLnbObj));
- if (!env->IsSameObject(lnb, nullptr)) {
+ ScopedLocalRef lnb(env, env->NewLocalRef(mLnbObj));
+ if (!env->IsSameObject(lnb.get(), nullptr)) {
env->CallVoidMethod(
- lnb,
+ lnb.get(),
gFields.onLnbEventID,
(jint)lnbEventType);
- env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onEvent:"
"Lnb object has been freed. Ignoring callback.");
@@ -388,17 +387,15 @@
void LnbClientCallbackImpl::onDiseqcMessage(const vector<uint8_t> &diseqcMessage) {
ALOGV("LnbClientCallbackImpl::onDiseqcMessage");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject lnb(env->NewLocalRef(mLnbObj));
- if (!env->IsSameObject(lnb, nullptr)) {
- jbyteArray array = env->NewByteArray(diseqcMessage.size());
- env->SetByteArrayRegion(array, 0, diseqcMessage.size(),
+ ScopedLocalRef lnb(env, env->NewLocalRef(mLnbObj));
+ if (!env->IsSameObject(lnb.get(), nullptr)) {
+ ScopedLocalRef array(env, env->NewByteArray(diseqcMessage.size()));
+ env->SetByteArrayRegion(array.get(), 0, diseqcMessage.size(),
reinterpret_cast<const jbyte *>(&diseqcMessage[0]));
env->CallVoidMethod(
- lnb,
+ lnb.get(),
gFields.onLnbDiseqcMessageID,
- array);
- env->DeleteLocalRef(lnb);
- env->DeleteLocalRef(array);
+ array.get());
} else {
ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
"Lnb object has been freed. Ignoring callback.");
@@ -422,10 +419,9 @@
void DvrClientCallbackImpl::onRecordStatus(RecordStatus status) {
ALOGV("DvrClientCallbackImpl::onRecordStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject dvr(env->NewLocalRef(mDvrObj));
- if (!env->IsSameObject(dvr, nullptr)) {
- env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
- env->DeleteLocalRef(dvr);
+ ScopedLocalRef dvr(env, env->NewLocalRef(mDvrObj));
+ if (!env->IsSameObject(dvr.get(), nullptr)) {
+ env->CallVoidMethod(dvr.get(), gFields.onDvrRecordStatusID, (jint)status);
} else {
ALOGE("DvrClientCallbackImpl::onRecordStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -435,10 +431,9 @@
void DvrClientCallbackImpl::onPlaybackStatus(PlaybackStatus status) {
ALOGV("DvrClientCallbackImpl::onPlaybackStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject dvr(env->NewLocalRef(mDvrObj));
- if (!env->IsSameObject(dvr, nullptr)) {
- env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
- env->DeleteLocalRef(dvr);
+ ScopedLocalRef dvr(env, env->NewLocalRef(mDvrObj));
+ if (!env->IsSameObject(dvr.get(), nullptr)) {
+ env->CallVoidMethod(dvr.get(), gFields.onDvrPlaybackStatusID, (jint)status);
} else {
ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -614,7 +609,7 @@
}
/////////////// FilterClientCallbackImpl ///////////////////////
-void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getSectionEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -624,20 +619,20 @@
jint sectionNum = sectionEvent.sectionNum;
jlong dataLength = sectionEvent.dataLength;
- jobject obj = env->NewObject(mSectionEventClass, mSectionEventInitID, tableId, version,
- sectionNum, dataLength);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mSectionEventClass, mSectionEventInitID, tableId,
+ version, sectionNum, dataLength));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
- jobject audioDescriptor = nullptr;
+ ScopedLocalRef<jobject> audioDescriptor(env);
gAudioPresentationFields.init(env);
- jobject presentationsJObj = JAudioPresentationInfo::asJobject(env, gAudioPresentationFields);
+ ScopedLocalRef presentationsJObj(env, JAudioPresentationInfo::asJobject(
+ env, gAudioPresentationFields));
switch (mediaEvent.extraMetaData.getTag()) {
case DemuxFilterMediaEventExtraMetaData::Tag::audio: {
@@ -650,9 +645,9 @@
jbyte adGainFront = ad.adGainFront;
jbyte adGainSurround = ad.adGainSurround;
- audioDescriptor = env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID, adFade,
- adPan, versionTextTag, adGainCenter, adGainFront,
- adGainSurround);
+ audioDescriptor.reset(env->NewObject(mAudioDescriptorClass, mAudioDescriptorInitID,
+ adFade, adPan, versionTextTag, adGainCenter,
+ adGainFront, adGainSurround));
break;
}
case DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations: {
@@ -660,7 +655,7 @@
env, gAudioPresentationFields,
mediaEvent.extraMetaData
.get<DemuxFilterMediaEventExtraMetaData::Tag::audioPresentations>(),
- presentationsJObj);
+ presentationsJObj.get());
break;
}
default: {
@@ -693,31 +688,27 @@
sc = mediaEvent.scIndexMask.get<DemuxFilterScIndexMask::Tag::scVvc>();
}
- jobject obj = env->NewObject(mMediaEventClass, mMediaEventInitID, streamId, isPtsPresent, pts,
- isDtsPresent, dts, dataLength, offset, nullptr, isSecureMemory,
- avDataId, mpuSequenceNumber, isPesPrivateData, sc,
- audioDescriptor, presentationsJObj);
+ ScopedLocalRef obj(env, env->NewObject(mMediaEventClass, mMediaEventInitID, streamId,
+ isPtsPresent, pts, isDtsPresent, dts, dataLength,
+ offset, nullptr, isSecureMemory, avDataId,
+ mpuSequenceNumber, isPesPrivateData, sc,
+ audioDescriptor.get(), presentationsJObj.get()));
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
sp<MediaEvent> mediaEventSp =
new MediaEvent(mFilterClient, dupFromAidl(mediaEvent.avMemory),
- mediaEvent.avDataId, dataLength + offset, obj);
+ mediaEvent.avDataId, dataLength + offset, obj.get());
mediaEventSp->mAvHandleRefCnt++;
- env->SetLongField(obj, mMediaEventFieldContextID, (jlong)mediaEventSp.get());
- mediaEventSp->incStrong(obj);
+ env->SetLongField(obj.get(), mMediaEventFieldContextID, (jlong)mediaEventSp.get());
+ mediaEventSp->incStrong(obj.get());
}
- env->SetObjectArrayElement(arr, size, obj);
- if(audioDescriptor != nullptr) {
- env->DeleteLocalRef(audioDescriptor);
- }
- env->DeleteLocalRef(obj);
- env->DeleteLocalRef(presentationsJObj);
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getPesEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -726,13 +717,12 @@
jint dataLength = pesEvent.dataLength;
jint mpuSequenceNumber = pesEvent.mpuSequenceNumber;
- jobject obj = env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
- mpuSequenceNumber);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mPesEventClass, mPesEventInitID, streamId, dataLength,
+ mpuSequenceNumber));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getTsRecordEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -764,13 +754,12 @@
jlong pts = tsRecordEvent.pts;
jint firstMbInSlice = tsRecordEvent.firstMbInSlice;
- jobject obj = env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
- byteNumber, pts, firstMbInSlice);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mTsRecordEventClass, mTsRecordEventInitID, jpid, ts, sc,
+ byteNumber, pts, firstMbInSlice));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getMmtpRecordEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -783,13 +772,13 @@
jint firstMbInSlice = mmtpRecordEvent.firstMbInSlice;
jlong tsIndexMask = mmtpRecordEvent.tsIndexMask;
- jobject obj = env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID, scHevcIndexMask,
- byteNumber, mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mMmtpRecordEventClass, mMmtpRecordEventInitID,
+ scHevcIndexMask, byteNumber, mpuSequenceNumber, pts,
+ firstMbInSlice, tsIndexMask));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getDownloadEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -801,25 +790,25 @@
jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
jint dataLength = downloadEvent.dataLength;
- jobject obj = env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId, downloadId,
- mpuSequenceNumber, itemFragmentIndex, lastItemFragmentIndex,
- dataLength);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mDownloadEventClass, mDownloadEventInitID, itemId,
+ downloadId, mpuSequenceNumber, itemFragmentIndex,
+ lastItemFragmentIndex, dataLength));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getIpPayloadEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- const DemuxFilterIpPayloadEvent &ipPayloadEvent = event.get<DemuxFilterEvent::Tag::ipPayload>();
+ const DemuxFilterIpPayloadEvent &ipPayloadEvent =
+ event.get<DemuxFilterEvent::Tag::ipPayload>();
jint dataLength = ipPayloadEvent.dataLength;
- jobject obj = env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID, dataLength);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mIpPayloadEventClass, mIpPayloadEventInitID,
+ dataLength));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getTemiEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
@@ -828,110 +817,108 @@
jbyte descrTag = temiEvent.descrTag;
std::vector<uint8_t> descrData = temiEvent.descrData;
- jbyteArray array = env->NewByteArray(descrData.size());
- env->SetByteArrayRegion(array, 0, descrData.size(), reinterpret_cast<jbyte *>(&descrData[0]));
+ ScopedLocalRef array(env, env->NewByteArray(descrData.size()));
+ env->SetByteArrayRegion(array.get(), 0, descrData.size(),
+ reinterpret_cast<jbyte *>(&descrData[0]));
- jobject obj = env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag, array);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(array);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mTemiEventClass, mTemiEventInitID, pts, descrTag,
+ array.get()));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getScramblingStatusEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
const DemuxFilterMonitorEvent &scramblingStatus =
event.get<DemuxFilterEvent::Tag::monitorEvent>()
.get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
- jobject obj = env->NewObject(mScramblingStatusEventClass, mScramblingStatusEventInitID,
- scramblingStatus);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mScramblingStatusEventClass,
+ mScramblingStatusEventInitID,
+ scramblingStatus));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getIpCidChangeEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
const DemuxFilterMonitorEvent &cid = event.get<DemuxFilterEvent::Tag::monitorEvent>()
.get<DemuxFilterMonitorEvent::Tag::cid>();
- jobject obj = env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mIpCidChangeEventClass, mIpCidChangeEventInitID, cid));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
-void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
+void FilterClientCallbackImpl::getRestartEvent(const jobjectArray& arr, const int size,
const DemuxFilterEvent &event) {
JNIEnv *env = AndroidRuntime::getJNIEnv();
const int32_t &startId = event.get<DemuxFilterEvent::Tag::startId>();
- jobject obj = env->NewObject(mRestartEventClass, mRestartEventInitID, startId);
- env->SetObjectArrayElement(arr, size, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(mRestartEventClass, mRestartEventInitID, startId));
+ env->SetObjectArrayElement(arr, size, obj.get());
}
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
ALOGV("FilterClientCallbackImpl::onFilterEvent");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobjectArray array;
+ ScopedLocalRef<jobjectArray> array(env);
if (!events.empty()) {
- array = env->NewObjectArray(events.size(), mEventClass, nullptr);
+ array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr));
}
for (int i = 0, arraySize = 0; i < events.size(); i++) {
const DemuxFilterEvent &event = events[i];
switch (event.getTag()) {
case DemuxFilterEvent::Tag::media: {
- getMediaEvent(array, arraySize, event);
+ getMediaEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::section: {
- getSectionEvent(array, arraySize, event);
+ getSectionEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::pes: {
- getPesEvent(array, arraySize, event);
+ getPesEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::tsRecord: {
- getTsRecordEvent(array, arraySize, event);
+ getTsRecordEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::mmtpRecord: {
- getMmtpRecordEvent(array, arraySize, event);
+ getMmtpRecordEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::download: {
- getDownloadEvent(array, arraySize, event);
+ getDownloadEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::ipPayload: {
- getIpPayloadEvent(array, arraySize, event);
+ getIpPayloadEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::temi: {
- getTemiEvent(array, arraySize, event);
+ getTemiEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterEvent::Tag::monitorEvent: {
switch (event.get<DemuxFilterEvent::Tag::monitorEvent>().getTag()) {
case DemuxFilterMonitorEvent::Tag::scramblingStatus: {
- getScramblingStatusEvent(array, arraySize, event);
+ getScramblingStatusEvent(array.get(), arraySize, event);
arraySize++;
break;
}
case DemuxFilterMonitorEvent::Tag::cid: {
- getIpCidChangeEvent(array, arraySize, event);
+ getIpCidChangeEvent(array.get(), arraySize, event);
arraySize++;
break;
}
@@ -943,7 +930,7 @@
break;
}
case DemuxFilterEvent::Tag::startId: {
- getRestartEvent(array, arraySize, event);
+ getRestartEvent(array.get(), arraySize, event);
arraySize++;
break;
}
@@ -953,32 +940,29 @@
}
}
}
- jobject filter(env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter, nullptr)) {
+ ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
+ if (!env->IsSameObject(filter.get(), nullptr)) {
jmethodID methodID = gFields.onFilterEventID;
if (mSharedFilter) {
methodID = gFields.onSharedFilterEventID;
}
- env->CallVoidMethod(filter, methodID, array);
- env->DeleteLocalRef(filter);
+ env->CallVoidMethod(filter.get(), methodID, array.get());
} else {
ALOGE("FilterClientCallbackImpl::onFilterEvent:"
"Filter object has been freed. Ignoring callback.");
}
- env->DeleteLocalRef(array);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ALOGV("FilterClientCallbackImpl::onFilterStatus");
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jobject filter(env->NewLocalRef(mFilterObj));
- if (!env->IsSameObject(filter, nullptr)) {
+ ScopedLocalRef filter(env, env->NewLocalRef(mFilterObj));
+ if (!env->IsSameObject(filter.get(), nullptr)) {
jmethodID methodID = gFields.onFilterStatusID;
if (mSharedFilter) {
methodID = gFields.onSharedFilterStatusID;
}
- env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
- env->DeleteLocalRef(filter);
+ env->CallVoidMethod(filter.get(), methodID, (jint)static_cast<uint8_t>(status));
} else {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
@@ -1115,13 +1099,12 @@
std::scoped_lock<std::mutex> lock(mMutex);
for (const auto& mapEntry : mListenersMap) {
ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second);
- jobject frontend(env->NewLocalRef(mapEntry.second));
- if (!env->IsSameObject(frontend, nullptr)) {
+ ScopedLocalRef frontend(env, env->NewLocalRef(mapEntry.second));
+ if (!env->IsSameObject(frontend.get(), nullptr)) {
env->CallVoidMethod(
- frontend,
+ frontend.get(),
gFields.onFrontendEventID,
(jint)frontendEventType);
- env->DeleteLocalRef(frontend);
} else {
ALOGW("FrontendClientCallbackImpl::onEvent:"
"Frontend object has been freed. Ignoring callback.");
@@ -1133,20 +1116,18 @@
FrontendScanMessageType type, const FrontendScanMessage& message) {
ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type);
JNIEnv *env = AndroidRuntime::getJNIEnv();
- jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
+ ScopedLocalRef clazz(env, env->FindClass("android/media/tv/tuner/Tuner"));
std::scoped_lock<std::mutex> lock(mMutex);
for (const auto& mapEntry : mListenersMap) {
- jobject frontend(env->NewLocalRef(mapEntry.second));
- if (env->IsSameObject(frontend, nullptr)) {
+ ScopedLocalRef frontend(env, env->NewLocalRef(mapEntry.second));
+ if (env->IsSameObject(frontend.get(), nullptr)) {
ALOGE("FrontendClientCallbackImpl::onScanMessage:"
"Tuner object has been freed. Ignoring callback.");
continue;
}
- executeOnScanMessage(env, clazz, frontend, type, message);
- env->DeleteLocalRef(frontend);
+ executeOnScanMessage(env, clazz.get(), frontend.get(), type, message);
}
- env->DeleteLocalRef(clazz);
}
void FrontendClientCallbackImpl::executeOnScanMessage(
@@ -1183,20 +1164,19 @@
}
case FrontendScanMessageType::FREQUENCY: {
std::vector<int64_t> v = message.get<FrontendScanMessage::Tag::frequencies>();
- jlongArray freqs = env->NewLongArray(v.size());
- env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
+ ScopedLocalRef freqs(env, env->NewLongArray(v.size()));
+ env->SetLongArrayRegion(freqs.get(), 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
- freqs);
- env->DeleteLocalRef(freqs);
+ freqs.get());
break;
}
case FrontendScanMessageType::SYMBOL_RATE: {
std::vector<int32_t> v = message.get<FrontendScanMessage::Tag::symbolRates>();
- jintArray symbolRates = env->NewIntArray(v.size());
- env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+ ScopedLocalRef symbolRates(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(symbolRates.get(), 0, v.size(),
+ reinterpret_cast<jint *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
- symbolRates);
- env->DeleteLocalRef(symbolRates);
+ symbolRates.get());
break;
}
case FrontendScanMessageType::HIERARCHY: {
@@ -1211,27 +1191,29 @@
}
case FrontendScanMessageType::PLP_IDS: {
std::vector<int32_t> jintV = message.get<FrontendScanMessage::Tag::plpIds>();
- jintArray plpIds = env->NewIntArray(jintV.size());
- env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
- env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
- env->DeleteLocalRef(plpIds);
+ ScopedLocalRef plpIds(env, env->NewIntArray(jintV.size()));
+ env->SetIntArrayRegion(plpIds.get(), 0, jintV.size(),
+ reinterpret_cast<jint *>(&jintV[0]));
+ env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"),
+ plpIds.get());
break;
}
case FrontendScanMessageType::GROUP_IDS: {
std::vector<int32_t> jintV = message.get<FrontendScanMessage::groupIds>();
- jintArray groupIds = env->NewIntArray(jintV.size());
- env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
- env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
- env->DeleteLocalRef(groupIds);
+ ScopedLocalRef groupIds(env, env->NewIntArray(jintV.size()));
+ env->SetIntArrayRegion(groupIds.get(), 0, jintV.size(),
+ reinterpret_cast<jint *>(&jintV[0]));
+ env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"),
+ groupIds.get());
break;
}
case FrontendScanMessageType::INPUT_STREAM_IDS: {
std::vector<int32_t> jintV = message.get<FrontendScanMessage::inputStreamIds>();
- jintArray streamIds = env->NewIntArray(jintV.size());
- env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
+ ScopedLocalRef streamIds(env, env->NewIntArray(jintV.size()));
+ env->SetIntArrayRegion(streamIds.get(), 0, jintV.size(),
+ reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
- streamIds);
- env->DeleteLocalRef(streamIds);
+ streamIds.get());
break;
}
case FrontendScanMessageType::STANDARD: {
@@ -1254,26 +1236,25 @@
break;
}
case FrontendScanMessageType::ATSC3_PLP_INFO: {
- jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
- jmethodID init = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+ ScopedLocalRef plpClazz(env,
+ env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"));
+ jmethodID init = env->GetMethodID(plpClazz.get(), "<init>", "(IZ)V");
std::vector<FrontendScanAtsc3PlpInfo> plpInfos =
message.get<FrontendScanMessage::atsc3PlpInfos>();
- jobjectArray array = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+ ScopedLocalRef array(env,
+ env->NewObjectArray(plpInfos.size(), plpClazz.get(), nullptr));
for (int i = 0; i < plpInfos.size(); i++) {
const FrontendScanAtsc3PlpInfo &info = plpInfos[i];
jint plpId = info.plpId;
jboolean lls = info.bLlsFlag;
- jobject obj = env->NewObject(plpClazz, init, plpId, lls);
- env->SetObjectArrayElement(array, i, obj);
- env->DeleteLocalRef(obj);
+ ScopedLocalRef obj(env, env->NewObject(plpClazz.get(), init, plpId, lls));
+ env->SetObjectArrayElement(array.get(), i, obj.get());
}
env->CallVoidMethod(frontend,
env->GetMethodID(clazz, "onAtsc3PlpInfos",
"([Landroid/media/tv/tuner/frontend/"
"Atsc3PlpInfo;)V"),
- array);
- env->DeleteLocalRef(array);
- env->DeleteLocalRef(plpClazz);
+ array.get());
break;
}
case FrontendScanMessageType::MODULATION: {
@@ -1341,11 +1322,12 @@
}
case FrontendScanMessageType::DVBT_CELL_IDS: {
std::vector<int32_t> jintV = message.get<FrontendScanMessage::dvbtCellIds>();
- jintArray cellIds = env->NewIntArray(jintV.size());
- env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
- env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
- cellIds);
- env->DeleteLocalRef(cellIds);
+ ScopedLocalRef cellIds(env, env->NewIntArray(jintV.size()));
+ env->SetIntArrayRegion(cellIds.get(), 0, jintV.size(),
+ reinterpret_cast<jint *>(&jintV[0]));
+ env->CallVoidMethod(frontend,
+ env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
+ cellIds.get());
break;
}
default:
@@ -1434,7 +1416,8 @@
JNIEnv *env = AndroidRuntime::getJNIEnv();
jclass arrayListClazz = env->FindClass("java/util/ArrayList");
jmethodID arrayListAdd = env->GetMethodID(arrayListClazz, "add", "(Ljava/lang/Object;)Z");
- jobject obj = env->NewObject(arrayListClazz, env->GetMethodID(arrayListClazz, "<init>", "()V"));
+ jobject obj = env->NewObject(arrayListClazz,
+ env->GetMethodID(arrayListClazz, "<init>", "()V"));
jclass integerClazz = env->FindClass("java/lang/Integer");
jmethodID intInit = env->GetMethodID(integerClazz, "<init>", "(I)V");
@@ -1672,7 +1655,7 @@
jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendInfo");
jmethodID infoInit =
env->GetMethodID(clazz, "<init>",
- "(IIJJIIJI[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V");
+ "(IIJJIIJI[ILandroid/media/tv/tuner/frontend/FrontendCapabilities;)V");
jint type = (jint)feInfo->type;
jlong minFrequency = feInfo->minFrequency;
@@ -1812,9 +1795,8 @@
jmethodID init = env->GetMethodID(clazz, "<init>", "(II)V");
jobjectArray valObj = env->NewObjectArray(size, clazz, nullptr);
for (int i = 0; i < size; i++) {
- jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
- env->SetObjectArrayElement(valObj, i, readinessObj);
- env->DeleteLocalRef(readinessObj);
+ ScopedLocalRef readinessObj(env, env->NewObject(clazz, init, intTypes[i], readiness[i]));
+ env->SetObjectArrayElement(valObj, i, readinessObj.get());
}
return valObj;
}
@@ -2260,79 +2242,72 @@
switch (s.getTag()) {
case FrontendStatus::Tag::isDemodLocked: {
jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isDemodLocked>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env,
+ env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isDemodLocked>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::snr: {
jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::ber: {
jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::per: {
jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::preBer: {
jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::signalQuality: {
jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;");
- jobject newIntegerObj = env->NewObject(intClazz, initInt,
- s.get<FrontendStatus::Tag::signalQuality>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt,
+ s.get<FrontendStatus::Tag::signalQuality>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::signalStrength: {
jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- s.get<FrontendStatus::Tag::signalStrength>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ s.get<FrontendStatus::Tag::signalStrength>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::symbolRate: {
jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt,
+ s.get<FrontendStatus::Tag::symbolRate>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::innerFec: {
jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;");
- jclass longClazz = env->FindClass("java/lang/Long");
- jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V");
- jobject newLongObj =
- env->NewObject(longClazz, initLong,
- static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
- env->SetObjectField(statusObj, field, newLongObj);
- env->DeleteLocalRef(newLongObj);
- env->DeleteLocalRef(longClazz);
+ ScopedLocalRef longClazz(env, env->FindClass("java/lang/Long"));
+ jmethodID initLong = env->GetMethodID(longClazz.get(), "<init>", "(J)V");
+ ScopedLocalRef newLongObj(env,
+ env->NewObject(longClazz.get(), initLong,
+ static_cast<long>(s.get<FrontendStatus::Tag::innerFec>())));
+ env->SetObjectField(statusObj, field, newLongObj.get());
break;
}
case FrontendStatus::Tag::modulationStatus: {
@@ -2373,139 +2348,128 @@
}
}
if (valid) {
- jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, intModulation));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
}
break;
}
case FrontendStatus::Tag::inversion: {
jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ static_cast<jint>(s.get<FrontendStatus::Tag::inversion>())));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::lnbVoltage: {
jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>())));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::plpId: {
jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::isEWBS: {
jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isEWBS>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isEWBS>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::agc: {
jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::isLnaOn: {
jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isLnaOn>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isLnaOn>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::isLayerError: {
jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z");
vector<bool> layerErr = s.get<FrontendStatus::Tag::isLayerError>();
- jbooleanArray valObj = env->NewBooleanArray(layerErr.size());
+ ScopedLocalRef valObj(env, env->NewBooleanArray(layerErr.size()));
for (size_t i = 0; i < layerErr.size(); i++) {
jboolean x = layerErr[i];
- env->SetBooleanArrayRegion(valObj, i, 1, &x);
+ env->SetBooleanArrayRegion(valObj.get(), i, 1, &x);
}
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::mer: {
jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::freqOffset: {
jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Long;");
- jobject newLongObj = env->NewObject(longClazz, initLong,
- s.get<FrontendStatus::Tag::freqOffset>());
- env->SetObjectField(statusObj, field, newLongObj);
- env->DeleteLocalRef(newLongObj);
+ ScopedLocalRef newLongObj(env, env->NewObject(longClazz, initLong,
+ s.get<FrontendStatus::Tag::freqOffset>()));
+ env->SetObjectField(statusObj, field, newLongObj.get());
break;
}
case FrontendStatus::Tag::hierarchy: {
jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>())));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::isRfLocked: {
jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isRfLocked>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isRfLocked>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::plpInfo: {
jfieldID field = env->GetFieldID(clazz, "mPlpInfo",
"[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;");
- jclass plpClazz = env->FindClass(
- "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo");
- jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V");
+ ScopedLocalRef plpClazz(env, env->FindClass(
+ "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo"));
+ jmethodID initPlp = env->GetMethodID(plpClazz.get(), "<init>", "(IZI)V");
- vector<FrontendStatusAtsc3PlpInfo> plpInfos = s.get<FrontendStatus::Tag::plpInfo>();
- jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+ vector<FrontendStatusAtsc3PlpInfo> plpInfos =
+ s.get<FrontendStatus::Tag::plpInfo>();
+ ScopedLocalRef valObj(env, env->NewObjectArray(plpInfos.size(), plpClazz.get(),
+ nullptr));
for (int i = 0; i < plpInfos.size(); i++) {
const FrontendStatusAtsc3PlpInfo &info = plpInfos[i];
jint plpId = info.plpId;
jboolean isLocked = info.isLocked;
jint uec = info.uec;
- jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
- env->SetObjectArrayElement(valObj, i, plpObj);
- env->DeleteLocalRef(plpObj);
+ ScopedLocalRef plpObj(env, env->NewObject(plpClazz.get(), initPlp, plpId,
+ isLocked, uec));
+ env->SetObjectArrayElement(valObj.get(), i, plpObj.get());
}
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
- env->DeleteLocalRef(plpClazz);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::modulations: {
jfieldID field = env->GetFieldID(clazz, "mModulationsExt", "[I");
std::vector<FrontendModulation> v = s.get<FrontendStatus::Tag::modulations>();
- jintArray valObj = env->NewIntArray(v.size());
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
bool valid = false;
jint m[1];
for (int i = 0; i < v.size(); i++) {
@@ -2514,63 +2478,63 @@
case FrontendModulation::Tag::dvbc: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::dvbc>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::dvbs: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::dvbs>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::dvbt: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::dvbt>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::isdbs: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::isdbs>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::isdbs3: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::isdbs3>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::isdbt: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::isdbt>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::atsc: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::atsc>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::atsc3: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::atsc3>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
case FrontendModulation::Tag::dtmb: {
m[0] = static_cast<jint>(
modulation.get<FrontendModulation::Tag::dtmb>());
- env->SetIntArrayRegion(valObj, i, 1, m);
+ env->SetIntArrayRegion(valObj.get(), i, 1, m);
valid = true;
break;
}
@@ -2579,31 +2543,28 @@
}
}
if (valid) {
- env->SetObjectField(statusObj, field, valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
}
- env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bers: {
jfieldID field = env->GetFieldID(clazz, "mBers", "[I");
std::vector<int32_t> v = s.get<FrontendStatus::Tag::bers>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::codeRates: {
jfieldID field = env->GetFieldID(clazz, "mCodeRates", "[I");
std::vector<FrontendInnerFec> v = s.get<FrontendStatus::Tag::codeRates>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::bandwidth: {
@@ -2642,9 +2603,9 @@
break;
}
if (valid) {
- jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+ intBandwidth));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
}
break;
}
@@ -2655,8 +2616,8 @@
bool valid = true;
switch (interval.getTag()) {
case FrontendGuardInterval::Tag::dvbt: {
- intInterval =
- static_cast<jint>(interval.get<FrontendGuardInterval::Tag::dvbt>());
+ intInterval = static_cast<jint>(
+ interval.get<FrontendGuardInterval::Tag::dvbt>());
break;
}
case FrontendGuardInterval::Tag::isdbt: {
@@ -2665,8 +2626,8 @@
break;
}
case FrontendGuardInterval::Tag::dtmb: {
- intInterval =
- static_cast<jint>(interval.get<FrontendGuardInterval::Tag::dtmb>());
+ intInterval = static_cast<jint>(
+ interval.get<FrontendGuardInterval::Tag::dtmb>());
break;
}
default:
@@ -2674,14 +2635,15 @@
break;
}
if (valid) {
- jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+ intInterval));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
}
break;
}
case FrontendStatus::Tag::transmissionMode: {
- jfieldID field = env->GetFieldID(clazz, "mTransmissionMode", "Ljava/lang/Integer;");
+ jfieldID field = env->GetFieldID(clazz, "mTransmissionMode",
+ "Ljava/lang/Integer;");
const FrontendTransmissionMode &transmissionMode =
s.get<FrontendStatus::Tag::transmissionMode>();
jint intTransmissionMode;
@@ -2707,32 +2669,30 @@
break;
}
if (valid) {
- jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+ intTransmissionMode));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
}
break;
}
case FrontendStatus::Tag::uec: {
jfieldID field = env->GetFieldID(clazz, "mUec", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::systemId: {
jfieldID field = env->GetFieldID(clazz, "mSystemId", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::interleaving: {
jfieldID field = env->GetFieldID(clazz, "mInterleaving", "[I");
std::vector<FrontendInterleaveMode> v = s.get<FrontendStatus::Tag::interleaving>();
- jintArray valObj = env->NewIntArray(v.size());
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
bool valid = false;
jint in[1];
for (int i = 0; i < v.size(); i++) {
@@ -2741,28 +2701,28 @@
case FrontendInterleaveMode::Tag::atsc3: {
in[0] = static_cast<jint>(
interleaving.get<FrontendInterleaveMode::Tag::atsc3>());
- env->SetIntArrayRegion(valObj, i, 1, in);
+ env->SetIntArrayRegion(valObj.get(), i, 1, in);
valid = true;
break;
}
case FrontendInterleaveMode::Tag::dvbc: {
in[0] = static_cast<jint>(
interleaving.get<FrontendInterleaveMode::Tag::dvbc>());
- env->SetIntArrayRegion(valObj, i, 1, in);
+ env->SetIntArrayRegion(valObj.get(), i, 1, in);
valid = true;
break;
}
case FrontendInterleaveMode::Tag::dtmb: {
in[0] = static_cast<jint>(
interleaving.get<FrontendInterleaveMode::Tag::dtmb>());
- env->SetIntArrayRegion(valObj, i, 1, in);
+ env->SetIntArrayRegion(valObj.get(), i, 1, in);
valid = true;
break;
}
case FrontendInterleaveMode::Tag::isdbt: {
in[0] = static_cast<jint>(
interleaving.get<FrontendInterleaveMode::Tag::isdbt>());
- env->SetIntArrayRegion(valObj, i, 1, in);
+ env->SetIntArrayRegion(valObj.get(), i, 1, in);
valid = true;
break;
}
@@ -2771,31 +2731,28 @@
}
}
if (valid) {
- env->SetObjectField(statusObj, field, valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
}
- env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::isdbtSegment: {
jfieldID field = env->GetFieldID(clazz, "mIsdbtSegment", "[I");
std::vector<int32_t> v = s.get<FrontendStatus::Tag::isdbtSegment>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint*>(&v[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::tsDataRate: {
jfieldID field = env->GetFieldID(clazz, "mTsDataRate", "[I");
std::vector<int32_t> v = s.get<FrontendStatus::Tag::tsDataRate>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(), reinterpret_cast<jint *>(&v[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::rollOff: {
@@ -2813,7 +2770,8 @@
break;
}
case FrontendRollOff::Tag::isdbs3: {
- intRollOff = static_cast<jint>(rollOff.get<FrontendRollOff::Tag::isdbs3>());
+ intRollOff = static_cast<jint>(
+ rollOff.get<FrontendRollOff::Tag::isdbs3>());
break;
}
default:
@@ -2821,141 +2779,135 @@
break;
}
if (valid) {
- jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env, env->NewObject(intClazz, initInt,
+ intRollOff));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
}
break;
}
case FrontendStatus::Tag::isMiso: {
jfieldID field = env->GetFieldID(clazz, "mIsMisoEnabled", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isMiso>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isMiso>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::isLinear: {
jfieldID field = env->GetFieldID(clazz, "mIsLinear", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isLinear>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env, env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isLinear>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::isShortFrames: {
jfieldID field = env->GetFieldID(clazz, "mIsShortFrames", "Ljava/lang/Boolean;");
- jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
- s.get<FrontendStatus::Tag::isShortFrames>());
- env->SetObjectField(statusObj, field, newBooleanObj);
- env->DeleteLocalRef(newBooleanObj);
+ ScopedLocalRef newBooleanObj(env,
+ env->NewObject(booleanClazz, initBoolean,
+ s.get<FrontendStatus::Tag::isShortFrames>()));
+ env->SetObjectField(statusObj, field, newBooleanObj.get());
break;
}
case FrontendStatus::Tag::isdbtMode: {
jfieldID field = env->GetFieldID(clazz, "mIsdbtMode", "Ljava/lang/Integer;");
- jobject newIntegerObj =
- env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ ScopedLocalRef newIntegerObj(env,
+ env->NewObject(intClazz, initInt,
+ s.get<FrontendStatus::Tag::isdbtMode>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::partialReceptionFlag: {
jfieldID field =
- env->GetFieldID(clazz, "mIsdbtPartialReceptionFlag", "Ljava/lang/Integer;");
- jobject newIntegerObj =
+ env->GetFieldID(clazz, "mIsdbtPartialReceptionFlag",
+ "Ljava/lang/Integer;");
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- s.get<FrontendStatus::Tag::partialReceptionFlag>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ s.get<FrontendStatus::Tag::partialReceptionFlag>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::streamIdList: {
jfieldID field = env->GetFieldID(clazz, "mStreamIds", "[I");
std::vector<int32_t> ids = s.get<FrontendStatus::Tag::streamIdList>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(),
+ reinterpret_cast<jint *>(&ids[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::dvbtCellIds: {
jfieldID field = env->GetFieldID(clazz, "mDvbtCellIds", "[I");
std::vector<int32_t> ids = s.get<FrontendStatus::Tag::dvbtCellIds>();
- jintArray valObj = env->NewIntArray(v.size());
- env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+ ScopedLocalRef valObj(env, env->NewIntArray(v.size()));
+ env->SetIntArrayRegion(valObj.get(), 0, v.size(),
+ reinterpret_cast<jint *>(&ids[0]));
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::allPlpInfo: {
jfieldID field = env->GetFieldID(clazz, "mAllPlpInfo",
- "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;");
- jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
- jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+ "[Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;");
+ ScopedLocalRef plpClazz(env,
+ env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo"));
+ jmethodID initPlp = env->GetMethodID(plpClazz.get(), "<init>", "(IZ)V");
vector<FrontendScanAtsc3PlpInfo> plpInfos =
s.get<FrontendStatus::Tag::allPlpInfo>();
- jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, nullptr);
+ ScopedLocalRef valObj(env, env->NewObjectArray(plpInfos.size(), plpClazz.get(),
+ nullptr));
for (int i = 0; i < plpInfos.size(); i++) {
- jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
- plpInfos[i].bLlsFlag);
- env->SetObjectArrayElement(valObj, i, plpObj);
- env->DeleteLocalRef(plpObj);
+ ScopedLocalRef plpObj(env, env->NewObject(plpClazz.get(), initPlp,
+ plpInfos[i].plpId,
+ plpInfos[i].bLlsFlag));
+ env->SetObjectArrayElement(valObj.get(), i, plpObj.get());
}
- env->SetObjectField(statusObj, field, valObj);
- env->DeleteLocalRef(valObj);
- env->DeleteLocalRef(plpClazz);
+ env->SetObjectField(statusObj, field, valObj.get());
break;
}
case FrontendStatus::Tag::iptvContentUrl: {
jfieldID field = env->GetFieldID(clazz, "mIptvContentUrl", "Ljava/lang/String;");
std::string iptvContentUrl = s.get<FrontendStatus::Tag::iptvContentUrl>();
- jstring iptvContentUrlUtf8 = env->NewStringUTF(iptvContentUrl.c_str());
- env->SetObjectField(statusObj, field, iptvContentUrlUtf8);
- env->DeleteLocalRef(iptvContentUrlUtf8);
+ ScopedLocalRef iptvContentUrlUtf8(env, env->NewStringUTF(iptvContentUrl.c_str()));
+ env->SetObjectField(statusObj, field, iptvContentUrlUtf8.get());
break;
}
case FrontendStatus::Tag::iptvPacketsLost: {
jfieldID field = env->GetFieldID(clazz, "mIptvPacketsLost", "Ljava/lang/Long;");
- jobject newLongObj =
+ ScopedLocalRef newLongObj(env,
env->NewObject(longClazz, initLong,
- s.get<FrontendStatus::Tag::iptvPacketsLost>());
- env->SetObjectField(statusObj, field, newLongObj);
- env->DeleteLocalRef(newLongObj);
+ s.get<FrontendStatus::Tag::iptvPacketsLost>()));
+ env->SetObjectField(statusObj, field, newLongObj.get());
break;
}
case FrontendStatus::Tag::iptvPacketsReceived: {
- jfieldID field = env->GetFieldID(clazz, "mIptvPacketsReceived", "Ljava/lang/Long;");
- jobject newLongObj =
+ jfieldID field = env->GetFieldID(clazz, "mIptvPacketsReceived",
+ "Ljava/lang/Long;");
+ ScopedLocalRef newLongObj(env,
env->NewObject(longClazz, initLong,
- s.get<FrontendStatus::Tag::iptvPacketsReceived>());
- env->SetObjectField(statusObj, field, newLongObj);
- env->DeleteLocalRef(newLongObj);
+ s.get<FrontendStatus::Tag::iptvPacketsReceived>()));
+ env->SetObjectField(statusObj, field, newLongObj.get());
break;
}
case FrontendStatus::Tag::iptvWorstJitterMs: {
jfieldID field = env->GetFieldID(clazz, "mIptvWorstJitterMs",
"Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- s.get<FrontendStatus::Tag::iptvWorstJitterMs>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ s.get<FrontendStatus::Tag::iptvWorstJitterMs>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
case FrontendStatus::Tag::iptvAverageJitterMs: {
jfieldID field = env->GetFieldID(clazz, "mIptvAverageJitterMs",
"Ljava/lang/Integer;");
- jobject newIntegerObj =
+ ScopedLocalRef newIntegerObj(env,
env->NewObject(intClazz, initInt,
- s.get<FrontendStatus::Tag::iptvAverageJitterMs>());
- env->SetObjectField(statusObj, field, newIntegerObj);
- env->DeleteLocalRef(newIntegerObj);
+ s.get<FrontendStatus::Tag::iptvAverageJitterMs>()));
+ env->SetObjectField(statusObj, field, newIntegerObj.get());
break;
}
}
@@ -3089,21 +3041,22 @@
vector<FrontendAtsc3PlpSettings> plps = vector<FrontendAtsc3PlpSettings>(len);
// parse PLP settings
for (int i = 0; i < len; i++) {
- jobject plp = env->GetObjectArrayElement(plpSettings, i);
- int32_t plpId = env->GetIntField(plp, env->GetFieldID(plpClazz, "mPlpId", "I"));
+ ScopedLocalRef plp(env, env->GetObjectArrayElement(plpSettings, i));
+ int32_t plpId = env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mPlpId", "I"));
FrontendAtsc3Modulation modulation =
static_cast<FrontendAtsc3Modulation>(
- env->GetIntField(plp, env->GetFieldID(plpClazz, "mModulation", "I")));
+ env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mModulation",
+ "I")));
FrontendAtsc3TimeInterleaveMode interleaveMode =
static_cast<FrontendAtsc3TimeInterleaveMode>(
env->GetIntField(
- plp, env->GetFieldID(plpClazz, "mInterleaveMode", "I")));
+ plp.get(), env->GetFieldID(plpClazz, "mInterleaveMode", "I")));
FrontendAtsc3CodeRate codeRate =
static_cast<FrontendAtsc3CodeRate>(
- env->GetIntField(plp, env->GetFieldID(plpClazz, "mCodeRate", "I")));
+ env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mCodeRate", "I")));
FrontendAtsc3Fec fec =
static_cast<FrontendAtsc3Fec>(
- env->GetIntField(plp, env->GetFieldID(plpClazz, "mFec", "I")));
+ env->GetIntField(plp.get(), env->GetFieldID(plpClazz, "mFec", "I")));
FrontendAtsc3PlpSettings frontendAtsc3PlpSettings {
.plpId = plpId,
.modulation = modulation,
@@ -3112,7 +3065,6 @@
.fec = fec,
};
plps[i] = frontendAtsc3PlpSettings;
- env->DeleteLocalRef(plp);
}
return plps;
}
@@ -3457,18 +3409,17 @@
"android/media/tv/tuner/frontend/IsdbtFrontendSettings$IsdbtLayerSettings");
frontendIsdbtSettings.layerSettings.resize(len);
for (int i = 0; i < len; i++) {
- jobject layer = env->GetObjectArrayElement(layerSettings, i);
+ ScopedLocalRef layer(env, env->GetObjectArrayElement(layerSettings, i));
frontendIsdbtSettings.layerSettings[i].modulation = static_cast<FrontendIsdbtModulation>(
- env->GetIntField(layer, env->GetFieldID(layerClazz, "mModulation", "I")));
+ env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mModulation", "I")));
frontendIsdbtSettings.layerSettings[i].timeInterleave =
static_cast<FrontendIsdbtTimeInterleaveMode>(
- env->GetIntField(layer,
+ env->GetIntField(layer.get(),
env->GetFieldID(layerClazz, "mTimeInterleaveMode", "I")));
frontendIsdbtSettings.layerSettings[i].coderate = static_cast<FrontendIsdbtCoderate>(
- env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
+ env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mCodeRate", "I")));
frontendIsdbtSettings.layerSettings[i].numOfSegment =
- env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
- env->DeleteLocalRef(layer);
+ env->GetIntField(layer.get(), env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
}
frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
@@ -3498,7 +3449,8 @@
env->GetIntField(settings, env->GetFieldID(clazz, "mGuardInterval", "I")));
FrontendDtmbTimeInterleaveMode interleaveMode =
static_cast<FrontendDtmbTimeInterleaveMode>(
- env->GetIntField(settings, env->GetFieldID(clazz, "mTimeInterleaveMode", "I")));
+ env->GetIntField(settings, env->GetFieldID(clazz, "mTimeInterleaveMode",
+ "I")));
FrontendDtmbSettings frontendDtmbSettings{
.frequency = freq,
@@ -3515,7 +3467,8 @@
return frontendSettings;
}
-static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config, const char* className) {
+static DemuxIpAddress getDemuxIpAddress(JNIEnv *env, const jobject& config,
+ const char* className) {
jclass clazz = env->FindClass(className);
jbyteArray jsrcIpAddress = static_cast<jbyteArray>(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 6b1b6b1..01c998d 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -163,17 +163,19 @@
jmethodID mRestartEventInitID;
jfieldID mMediaEventFieldContextID;
bool mSharedFilter;
- void getSectionEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getMediaEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getPesEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getTsRecordEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getMmtpRecordEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getDownloadEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getIpPayloadEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getTemiEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getScramblingStatusEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getIpCidChangeEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
- void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getSectionEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getMediaEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getPesEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getTsRecordEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getMmtpRecordEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getDownloadEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getIpPayloadEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getTemiEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
+ void getScramblingStatusEvent(const jobjectArray& arr, const int size,
+ const DemuxFilterEvent& event);
+ void getIpCidChangeEvent(const jobjectArray& arr, const int size,
+ const DemuxFilterEvent& event);
+ void getRestartEvent(const jobjectArray& arr, const int size, const DemuxFilterEvent& event);
};
struct JTuner;
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index b8887390..946185a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -24,6 +24,7 @@
import android.os.Bundle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.WebView;
@@ -183,7 +184,14 @@
setContentView(mWebView);
// Load the URL
- mWebView.loadUrl(mUrl.toString());
+ String userData = mIntent.getStringExtra(SlicePurchaseController.EXTRA_USER_DATA);
+ if (TextUtils.isEmpty(userData)) {
+ logd("Starting WebView with url: " + mUrl.toString());
+ mWebView.loadUrl(mUrl.toString());
+ } else {
+ logd("Starting WebView with url: " + mUrl.toString() + ", userData=" + userData);
+ mWebView.postUrl(mUrl.toString(), userData.getBytes());
+ }
}
private static void logd(@NonNull String s) {
diff --git a/packages/SettingsLib/ActivityEmbedding/OWNERS b/packages/SettingsLib/ActivityEmbedding/OWNERS
index 7022402..6f1ab9b 100644
--- a/packages/SettingsLib/ActivityEmbedding/OWNERS
+++ b/packages/SettingsLib/ActivityEmbedding/OWNERS
@@ -1,5 +1,4 @@
# Default reviewers for this and subdirectories.
-arcwang@google.com
-chiujason@google.com
+sunnyshao@google.com
# Emergency approvers in case the above are not available
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/OWNERS b/packages/SettingsLib/OWNERS
index 81340f5..b2b7b61 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -1,4 +1,7 @@
# People who can approve changes for submission
+cantol@google.com
+chiujason@google.com
+cipson@google.com
dsandler@android.com
edgarwang@google.com
evanlaird@google.com
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/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
index c6e13a1..79635a0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -20,12 +20,20 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.TabRow
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalConfiguration
import com.android.settingslib.spa.framework.theme.SettingsDimension
import kotlin.math.absoluteValue
import kotlinx.coroutines.launch
@@ -41,7 +49,7 @@
Column {
val coroutineScope = rememberCoroutineScope()
- val pagerState = rememberPagerState()
+ val pagerState = rememberPageStateFixed()
TabRow(
selectedTabIndex = pagerState.currentPage,
@@ -69,3 +77,41 @@
}
}
}
+
+/**
+ * Gets the state of [PagerState].
+ *
+ * This is a work around.
+ *
+ * TODO: Remove this and replace with rememberPageState() after the Compose Foundation 1.5.0-alpha04
+ * updated in the platform.
+ */
+@Composable
+@OptIn(ExperimentalFoundationApi::class)
+private fun rememberPageStateFixed(): PagerState {
+ var currentPage by rememberSaveable { mutableStateOf(0) }
+ var targetPage by rememberSaveable { mutableStateOf(-1) }
+ val pagerState = rememberPagerState()
+ val configuration = LocalConfiguration.current
+ var lastScreenWidthDp by rememberSaveable { mutableStateOf(-1) }
+ val screenWidthDp = configuration.screenWidthDp
+ LaunchedEffect(screenWidthDp) {
+ // Reset pager state to fix an issue after configuration change.
+ // When we declare android:configChanges in the manifest, the pager state is in a weird
+ // state after configuration change.
+ targetPage = currentPage
+ lastScreenWidthDp = screenWidthDp
+ }
+ LaunchedEffect(targetPage) {
+ if (targetPage != -1) {
+ pagerState.scrollToPage(targetPage)
+ targetPage = -1
+ }
+ }
+ SideEffect {
+ if (lastScreenWidthDp == screenWidthDp) {
+ currentPage = pagerState.currentPage
+ }
+ }
+ return pagerState
+}
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/res/drawable/ic_person_add.xml b/packages/SettingsLib/res/drawable/ic_person_add.xml
new file mode 100644
index 0000000..d138c69
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_person_add.xml
@@ -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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,14V11H15V9H18V6H20V9H23V11H20V14ZM9,12Q7.35,12 6.175,10.825Q5,9.65 5,8Q5,6.35 6.175,5.175Q7.35,4 9,4Q10.65,4 11.825,5.175Q13,6.35 13,8Q13,9.65 11.825,10.825Q10.65,12 9,12ZM1,20V17.2Q1,16.35 1.438,15.637Q1.875,14.925 2.6,14.55Q4.15,13.775 5.75,13.387Q7.35,13 9,13Q10.65,13 12.25,13.387Q13.85,13.775 15.4,14.55Q16.125,14.925 16.562,15.637Q17,16.35 17,17.2V20ZM3,18H15V17.2Q15,16.925 14.863,16.7Q14.725,16.475 14.5,16.35Q13.15,15.675 11.775,15.337Q10.4,15 9,15Q7.6,15 6.225,15.337Q4.85,15.675 3.5,16.35Q3.275,16.475 3.138,16.7Q3,16.925 3,17.2ZM9,10Q9.825,10 10.413,9.412Q11,8.825 11,8Q11,7.175 10.413,6.588Q9.825,6 9,6Q8.175,6 7.588,6.588Q7,7.175 7,8Q7,8.825 7.588,9.412Q8.175,10 9,10ZM9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8Q9,8 9,8ZM9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Q9,18 9,18Z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml
index 9081ca5..54f8096 100644
--- a/packages/SettingsLib/res/layout/dialog_with_icon.xml
+++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml
@@ -13,87 +13,88 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:padding="@dimen/grant_admin_dialog_padding"
- android:paddingBottom="0dp">
- <ImageView
- android:id="@+id/dialog_with_icon_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription=""/>
- <TextView
- android:id="@+id/dialog_with_icon_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
- style="@style/DialogWithIconTitle"
- android:text="@string/user_grant_admin_title"
- android:textDirection="locale"/>
- <TextView
- android:id="@+id/dialog_with_icon_message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:gravity="center"
- style="@style/TextAppearanceSmall"
- android:text=""
- android:textDirection="locale"/>
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/new_user_dialog_id">
+
<LinearLayout
- android:id="@+id/custom_layout"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:paddingBottom="0dp">
- </LinearLayout>
- <LinearLayout
- android:id="@+id/button_panel"
- android:orientation="horizontal"
- android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- android:paddingBottom="0dp">
- <Button
- android:id="@+id/button_cancel"
- style="@style/DialogButtonNegative"
- android:layout_width="wrap_content"
- android:buttonCornerRadius="28dp"
- android:layout_height="wrap_content"
- android:visibility="gone"/>
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="1" >
- </Space>
- <Button
- android:id="@+id/button_back"
+ android:padding="@dimen/dialog_content_padding">
+ <ImageView
+ android:id="@+id/dialog_with_icon_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/DialogButtonNegative"
- android:buttonCornerRadius="40dp"
- android:clickable="true"
- android:focusable="true"
- android:text="Back"
- android:visibility="gone"
- />
- <Space
- android:layout_width="0dp"
- android:layout_height="1dp"
- android:layout_weight="0.05"
- >
- </Space>
- <Button
- android:id="@+id/button_ok"
+ android:importantForAccessibility="no"/>
+ <TextView
+ android:id="@+id/dialog_with_icon_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/DialogButtonPositive"
- android:clickable="true"
- android:focusable="true"
- android:visibility="gone"
- />
+ android:gravity="center"
+ style="@style/DialogWithIconTitle"/>
+ <TextView
+ android:id="@+id/dialog_with_icon_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:gravity="center"
+ style="@style/TextAppearanceSmall"/>
+
+ <LinearLayout
+ android:id="@+id/custom_layout"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center">
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/button_panel"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center">
+
+ <Button
+ android:id="@+id/button_cancel"
+ style="@style/DialogButtonNegative"
+ android:layout_width="wrap_content"
+ android:buttonCornerRadius="28dp"
+ android:layout_height="wrap_content"
+ android:visibility="gone"/>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="1">
+ </Space>
+
+ <Button
+ android:id="@+id/button_back"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/DialogButtonNegative"
+ android:buttonCornerRadius="40dp"
+ android:visibility="gone"/>
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="1dp"
+ android:layout_weight="0.05">
+ </Space>
+
+ <Button
+ android:id="@+id/button_ok"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/DialogButtonPositive"
+ android:visibility="gone"
+ />
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
+</ScrollView>
diff --git a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
index 33c2e4855..4ffaf1b 100644
--- a/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/edit_user_info_dialog_content.xml
@@ -28,7 +28,8 @@
android:orientation="vertical">
<TextView
android:id="@+id/user_info_title"
- android:layout_width="wrap_content"
+ android:gravity="center"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/EditUserDialogTitle"
android:text="@string/user_info_settings_title"
diff --git a/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml
index d6acac2..26d8245 100644
--- a/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml
+++ b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml
@@ -14,10 +14,14 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/grant_admin_view"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="@dimen/grant_admin_dialog_padding">
+ android:paddingLeft="@dimen/dialog_content_padding"
+ android:paddingBottom="@dimen/dialog_content_padding"
+ android:paddingRight="@dimen/dialog_content_padding">
+
<RadioGroup
android:id="@+id/choose_admin"
android:layout_width="match_parent"
@@ -25,13 +29,19 @@
android:orientation="vertical">
<RadioButton
android:id="@+id/grant_admin_yes"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="@dimen/radio_button_padding"
+ android:paddingBottom="@dimen/radio_button_padding"
+ android:paddingStart="@dimen/radio_button_padding_start"
android:text="@string/grant_admin"/>
<RadioButton
android:id="@+id/grant_admin_no"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:paddingTop="@dimen/radio_button_padding"
+ android:paddingBottom="@dimen/radio_button_padding"
+ android:paddingStart="@dimen/radio_button_padding_start"
android:text="@string/not_grant_admin"/>
</RadioGroup>
</LinearLayout>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 2372c80..91549d7 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -112,8 +112,9 @@
<dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
<dimen name="broadcast_dialog_margin">16dp</dimen>
- <!-- Size of grant admin privileges dialog padding -->
- <dimen name="grant_admin_dialog_padding">16dp</dimen>
+ <dimen name="dialog_content_padding">16dp</dimen>
+ <dimen name="radio_button_padding">12dp</dimen>
+ <dimen name="radio_button_padding_start">8dp</dimen>
<dimen name="dialog_button_horizontal_padding">16dp</dimen>
<dimen name="dialog_button_vertical_padding">8dp</dimen>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7b4c862..67e3e03 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -318,7 +318,10 @@
return mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
state);
}
- if (profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
+ if (profileId == BluetoothProfile.HEADSET
+ || profileId == BluetoothProfile.A2DP
+ || profileId == BluetoothProfile.LE_AUDIO
+ || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) {
return mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(cachedDevice,
state);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 356bb82..8269b56 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -28,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -83,14 +84,14 @@
boolean setMemberDeviceIfNeeded(CachedBluetoothDevice newDevice) {
final int groupId = newDevice.getGroupId();
if (isValidGroupId(groupId)) {
- final CachedBluetoothDevice CsipDevice = getCachedDevice(groupId);
- log("setMemberDeviceIfNeeded, main: " + CsipDevice + ", member: " + newDevice);
+ final CachedBluetoothDevice mainDevice = getCachedDevice(groupId);
+ log("setMemberDeviceIfNeeded, main: " + mainDevice + ", member: " + newDevice);
// Just add one of the coordinated set from a pair in the list that is shown in the UI.
// Once there is other devices with the same groupId, to add new device as member
// devices.
- if (CsipDevice != null) {
- CsipDevice.addMemberDevice(newDevice);
- newDevice.setName(CsipDevice.getName());
+ if (mainDevice != null) {
+ mainDevice.addMemberDevice(newDevice);
+ newDevice.setName(mainDevice.getName());
return true;
}
}
@@ -152,14 +153,7 @@
log("onGroupIdChanged: groupId is invalid");
return;
}
- log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString());
- List<CachedBluetoothDevice> memberDevicesList = getMemberDevicesList(groupId);
- CachedBluetoothDevice newMainDevice =
- getPreferredMainDeviceWithoutConectionState(groupId, memberDevicesList);
-
- log("onGroupIdChanged: The mainDevice= " + newMainDevice
- + " and the memberDevicesList of groupId= " + groupId + " =" + memberDevicesList);
- addMemberDevicesIntoMainDevice(memberDevicesList, newMainDevice);
+ updateRelationshipOfGroupDevices(groupId);
}
// @return {@code true}, the event is processed inside the method. It is for updating
@@ -168,61 +162,30 @@
boolean onProfileConnectionStateChangedIfProcessed(CachedBluetoothDevice cachedDevice,
int state) {
log("onProfileConnectionStateChangedIfProcessed: " + cachedDevice + ", state: " + state);
- switch (state) {
- case BluetoothProfile.STATE_CONNECTED:
- onGroupIdChanged(cachedDevice.getGroupId());
- CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
- if (mainDevice != null) {
- if (mainDevice.isConnected()) {
- // When main device exists and in connected state, receiving member device
- // connection. To refresh main device UI
- mainDevice.refresh();
- return true;
- } else {
- // When both LE Audio devices are disconnected, receiving member device
- // connection. To switch content and dispatch to notify UI change
- mBtManager.getEventManager().dispatchDeviceRemoved(mainDevice);
- mainDevice.switchMemberDeviceContent(cachedDevice);
- mainDevice.refresh();
- // It is necessary to do remove and add for updating the mapping on
- // preference and device
- mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
- return true;
- }
- }
- break;
- case BluetoothProfile.STATE_DISCONNECTED:
- mainDevice = findMainDevice(cachedDevice);
- if (mainDevice != null) {
- // When main device exists, receiving sub device disconnection
- // To update main device UI
- mainDevice.refresh();
- return true;
- }
- final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
- if (memberSet.isEmpty()) {
- break;
- }
- for (CachedBluetoothDevice device : memberSet) {
- if (device.isConnected()) {
- log("set device: " + device + " as the main device");
- // Main device is disconnected and sub device is connected
- // To copy data from sub device to main device
- mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
- cachedDevice.switchMemberDeviceContent(device);
- cachedDevice.refresh();
- // It is necessary to do remove and add for updating the mapping on
- // preference and device
- mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
- return true;
- }
- }
- break;
- default:
- // Do not handle this state.
+ if (state != BluetoothProfile.STATE_CONNECTED
+ && state != BluetoothProfile.STATE_DISCONNECTED) {
+ return false;
}
- return false;
+ return updateRelationshipOfGroupDevices(cachedDevice.getGroupId());
+ }
+
+ @VisibleForTesting
+ boolean updateRelationshipOfGroupDevices(int groupId) {
+ if (!isValidGroupId(groupId)) {
+ log("The device is not group.");
+ return false;
+ }
+ log("updateRelationshipOfGroupDevices: mCachedDevices list =" + mCachedDevices.toString());
+
+ // Get the preferred main device by getPreferredMainDeviceWithoutConectionState
+ List<CachedBluetoothDevice> groupDevicesList = getGroupDevicesFromAllOfDevicesList(groupId);
+ CachedBluetoothDevice preferredMainDevice =
+ getPreferredMainDevice(groupId, groupDevicesList);
+ log("The preferredMainDevice= " + preferredMainDevice
+ + " and the groupDevicesList of groupId= " + groupId
+ + " =" + groupDevicesList);
+ return addMemberDevicesIntoMainDevice(groupId, preferredMainDevice);
}
CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
@@ -262,114 +225,153 @@
return false;
}
- private List<CachedBluetoothDevice> getMemberDevicesList(int groupId) {
- return mCachedDevices.stream()
- .filter(cacheDevice -> cacheDevice.getGroupId() == groupId)
- .collect(Collectors.toList());
+ @VisibleForTesting
+ List<CachedBluetoothDevice> getGroupDevicesFromAllOfDevicesList(int groupId) {
+ List<CachedBluetoothDevice> groupDevicesList = new ArrayList<>();
+ if (!isValidGroupId(groupId)) {
+ return groupDevicesList;
+ }
+ for (CachedBluetoothDevice item : mCachedDevices) {
+ if (groupId != item.getGroupId()) {
+ continue;
+ }
+ groupDevicesList.add(item);
+ groupDevicesList.addAll(item.getMemberDevice());
+ }
+ return groupDevicesList;
}
- private CachedBluetoothDevice getPreferredMainDeviceWithoutConectionState(int groupId,
- List<CachedBluetoothDevice> memberDevicesList) {
- // First, priority connected lead device from LE profile
- // Second, the DUAL mode device which has A2DP/HFP and LE audio
- // Last, any one of LE device in the list.
- if (memberDevicesList == null || memberDevicesList.isEmpty()) {
+ @VisibleForTesting
+ CachedBluetoothDevice getPreferredMainDevice(int groupId,
+ List<CachedBluetoothDevice> groupDevicesList) {
+ // How to select the preferred main device?
+ // 1. The DUAL mode connected device which has A2DP/HFP and LE audio.
+ // 2. One of connected LE device in the list. Default is the lead device from LE profile.
+ // 3. If there is no connected device, then reset the relationship. Set the DUAL mode
+ // deviced as the main device. Otherwise, set any one of the device.
+ if (groupDevicesList == null || groupDevicesList.isEmpty()) {
return null;
}
+ CachedBluetoothDevice dualModeDevice = groupDevicesList.stream()
+ .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+ .anyMatch(profile -> profile instanceof LeAudioProfile))
+ .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
+ .anyMatch(profile -> profile instanceof A2dpProfile
+ || profile instanceof HeadsetProfile))
+ .findFirst().orElse(null);
+ if (dualModeDevice != null && dualModeDevice.isConnected()) {
+ log("getPreferredMainDevice: The connected DUAL mode device");
+ return dualModeDevice;
+ }
+
final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
- final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT())
+ final BluetoothDevice leAudioLeadDevice = (leAudioProfile != null && isAtLeastT())
? leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
- if (mainBluetoothDevice != null) {
+ if (leAudioLeadDevice != null) {
log("getPreferredMainDevice: The LeadDevice from LE profile is "
- + mainBluetoothDevice.getAnonymizedAddress());
+ + leAudioLeadDevice.getAnonymizedAddress());
}
-
- // 1st
- CachedBluetoothDevice newMainDevice =
- mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
- if (newMainDevice != null) {
- if (newMainDevice.isConnected()) {
- log("getPreferredMainDevice: The connected LeadDevice from LE profile");
- return newMainDevice;
- } else {
- log("getPreferredMainDevice: The LeadDevice is not connect.");
- }
- } else {
+ CachedBluetoothDevice leAudioLeadCachedDevice =
+ leAudioLeadDevice != null ? deviceManager.findDevice(leAudioLeadDevice) : null;
+ if (leAudioLeadCachedDevice == null) {
log("getPreferredMainDevice: The LeadDevice is not in the all of devices list");
+ } else if (leAudioLeadCachedDevice.isConnected()) {
+ log("getPreferredMainDevice: The connected LeadDevice from LE profile");
+ return leAudioLeadCachedDevice;
}
-
- // 2nd
- newMainDevice = memberDevicesList.stream()
- .filter(cachedDevice -> cachedDevice.getConnectableProfiles().stream()
- .anyMatch(profile -> profile instanceof A2dpProfile
- || profile instanceof HeadsetProfile))
+ CachedBluetoothDevice oneOfConnectedDevices = groupDevicesList.stream()
+ .filter(cachedDevice -> cachedDevice.isConnected())
.findFirst().orElse(null);
- if (newMainDevice != null) {
- log("getPreferredMainDevice: The DUAL mode device");
- return newMainDevice;
+ if (oneOfConnectedDevices != null) {
+ log("getPreferredMainDevice: One of the connected devices.");
+ return oneOfConnectedDevices;
}
- // last
- if (!memberDevicesList.isEmpty()) {
- newMainDevice = memberDevicesList.get(0);
+ if (dualModeDevice != null) {
+ log("getPreferredMainDevice: The DUAL mode device.");
+ return dualModeDevice;
}
- return newMainDevice;
+ // last
+ if (!groupDevicesList.isEmpty()) {
+ log("getPreferredMainDevice: One of the group devices.");
+ return groupDevicesList.get(0);
+ }
+ return null;
}
- private void addMemberDevicesIntoMainDevice(List<CachedBluetoothDevice> memberDevicesList,
- CachedBluetoothDevice newMainDevice) {
- if (newMainDevice == null) {
+ @VisibleForTesting
+ boolean addMemberDevicesIntoMainDevice(int groupId, CachedBluetoothDevice preferredMainDevice) {
+ boolean hasChanged = false;
+ if (preferredMainDevice == null) {
log("addMemberDevicesIntoMainDevice: No main device. Do nothing.");
- return;
+ return hasChanged;
}
- if (memberDevicesList.isEmpty()) {
- log("addMemberDevicesIntoMainDevice: No member device in list. Do nothing.");
- return;
- }
- CachedBluetoothDevice mainDeviceOfNewMainDevice = findMainDevice(newMainDevice);
- boolean isMemberInOtherMainDevice = mainDeviceOfNewMainDevice != null;
- if (!memberDevicesList.contains(newMainDevice) && isMemberInOtherMainDevice) {
- log("addMemberDevicesIntoMainDevice: The 'new main device' is not in list, and it is "
- + "the member at other device. Do switch main and member.");
+
+ // If the current main device is not preferred main device, then set it as new main device.
+ // Otherwise, do nothing.
+ BluetoothDevice bluetoothDeviceOfPreferredMainDevice = preferredMainDevice.getDevice();
+ CachedBluetoothDevice mainDeviceOfPreferredMainDevice = findMainDevice(preferredMainDevice);
+ boolean hasPreferredMainDeviceAlreadyBeenMainDevice =
+ mainDeviceOfPreferredMainDevice == null;
+
+ if (!hasPreferredMainDeviceAlreadyBeenMainDevice) {
+ // preferredMainDevice has not been the main device.
+ // switch relationship between the mainDeviceOfPreferredMainDevice and
+ // PreferredMainDevice
+
+ log("addMemberDevicesIntoMainDevice: The PreferredMainDevice have the mainDevice. "
+ + "Do switch relationship between the mainDeviceOfPreferredMainDevice and "
+ + "PreferredMainDevice");
// To switch content and dispatch to notify UI change
- mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfNewMainDevice);
- mainDeviceOfNewMainDevice.switchMemberDeviceContent(newMainDevice);
- mainDeviceOfNewMainDevice.refresh();
+ mBtManager.getEventManager().dispatchDeviceRemoved(mainDeviceOfPreferredMainDevice);
+ mainDeviceOfPreferredMainDevice.switchMemberDeviceContent(preferredMainDevice);
+ mainDeviceOfPreferredMainDevice.refresh();
// It is necessary to do remove and add for updating the mapping on
// preference and device
- mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfNewMainDevice);
- } else {
- log("addMemberDevicesIntoMainDevice: Set new main device");
- for (CachedBluetoothDevice memberDeviceItem : memberDevicesList) {
- if (memberDeviceItem.equals(newMainDevice)) {
+ mBtManager.getEventManager().dispatchDeviceAdded(mainDeviceOfPreferredMainDevice);
+ hasChanged = true;
+ }
+
+ // If the mCachedDevices List at CachedBluetoothDeviceManager has multiple items which are
+ // the same groupId, then combine them and also keep the preferred main device as main
+ // device.
+ List<CachedBluetoothDevice> topLevelOfGroupDevicesList = mCachedDevices.stream()
+ .filter(device -> device.getGroupId() == groupId)
+ .collect(Collectors.toList());
+ boolean haveMultiMainDevicesInAllOfDevicesList = topLevelOfGroupDevicesList.size() > 1;
+ // Update the new main of CachedBluetoothDevice, since it may be changed in above step.
+ final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
+ preferredMainDevice = deviceManager.findDevice(bluetoothDeviceOfPreferredMainDevice);
+ if (haveMultiMainDevicesInAllOfDevicesList) {
+ // put another devices into main device.
+ for (CachedBluetoothDevice deviceItem : topLevelOfGroupDevicesList) {
+ if (deviceItem.getDevice() == null || deviceItem.getDevice().equals(
+ bluetoothDeviceOfPreferredMainDevice)) {
continue;
}
- Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
- if (!memberSet.isEmpty()) {
- for (CachedBluetoothDevice memberSetItem : memberSet) {
- if (!memberSetItem.equals(newMainDevice)) {
- newMainDevice.addMemberDevice(memberSetItem);
- }
+
+ Set<CachedBluetoothDevice> memberSet = deviceItem.getMemberDevice();
+ for (CachedBluetoothDevice memberSetItem : memberSet) {
+ if (!memberSetItem.equals(preferredMainDevice)) {
+ preferredMainDevice.addMemberDevice(memberSetItem);
}
- memberSet.clear();
}
-
- newMainDevice.addMemberDevice(memberDeviceItem);
- mCachedDevices.remove(memberDeviceItem);
- mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
- }
-
- if (!mCachedDevices.contains(newMainDevice)) {
- mCachedDevices.add(newMainDevice);
- mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
+ memberSet.clear();
+ preferredMainDevice.addMemberDevice(deviceItem);
+ mCachedDevices.remove(deviceItem);
+ mBtManager.getEventManager().dispatchDeviceRemoved(deviceItem);
+ hasChanged = true;
}
}
- log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
- + mCachedDevices);
+ if (hasChanged) {
+ log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
+ + mCachedDevices);
+ }
+ return hasChanged;
}
private void log(String msg) {
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/src/com/android/settingslib/users/CreateUserDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
new file mode 100644
index 0000000..e61c8f5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/CreateUserDialogController.java
@@ -0,0 +1,388 @@
+/*
+ * 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.settingslib.users;
+
+import android.annotation.IntDef;
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtilsInternal;
+import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.utils.CustomDialogHelper;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class encapsulates a Dialog for editing the user nickname and photo.
+ */
+public class CreateUserDialogController {
+
+ private static final String KEY_AWAITING_RESULT = "awaiting_result";
+ private static final String KEY_CURRENT_STATE = "current_state";
+ private static final String KEY_SAVED_PHOTO = "pending_photo";
+ private static final String KEY_SAVED_NAME = "saved_name";
+ private static final String KEY_IS_ADMIN = "admin_status";
+ private static final String KEY_ADD_USER_LONG_MESSAGE_DISPLAYED =
+ "key_add_user_long_message_displayed";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({EXIT_DIALOG, INITIAL_DIALOG, GRANT_ADMIN_DIALOG,
+ EDIT_NAME_DIALOG, CREATE_USER_AND_CLOSE})
+ public @interface AddUserState {}
+
+ private static final int EXIT_DIALOG = -1;
+ private static final int INITIAL_DIALOG = 0;
+ private static final int GRANT_ADMIN_DIALOG = 1;
+ private static final int EDIT_NAME_DIALOG = 2;
+ private static final int CREATE_USER_AND_CLOSE = 3;
+
+ private @AddUserState int mCurrentState;
+
+ private CustomDialogHelper mCustomDialogHelper;
+
+ private EditUserPhotoController mEditUserPhotoController;
+ private Bitmap mSavedPhoto;
+ private String mSavedName;
+ private Drawable mSavedDrawable;
+ private Boolean mIsAdmin;
+ private Dialog mUserCreationDialog;
+ private View mGrantAdminView;
+ private View mEditUserInfoView;
+ private EditText mUserNameView;
+ private Activity mActivity;
+ private ActivityStarter mActivityStarter;
+ private boolean mWaitingForActivityResult;
+ private NewUserData mSuccessCallback;
+
+ private final String mFileAuthority;
+
+ public CreateUserDialogController(String fileAuthority) {
+ mFileAuthority = fileAuthority;
+ }
+
+ /**
+ * Resets saved values.
+ */
+ public void clear() {
+ mUserCreationDialog = null;
+ mCustomDialogHelper = null;
+ mEditUserPhotoController = null;
+ mSavedPhoto = null;
+ mSavedName = null;
+ mSavedDrawable = null;
+ mIsAdmin = null;
+ mActivity = null;
+ mActivityStarter = null;
+ mGrantAdminView = null;
+ mEditUserInfoView = null;
+ mUserNameView = null;
+ mSuccessCallback = null;
+ mCurrentState = INITIAL_DIALOG;
+ }
+
+ /**
+ * Notifies that the containing activity or fragment was reinitialized.
+ */
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ String pendingPhoto = savedInstanceState.getString(KEY_SAVED_PHOTO);
+ if (pendingPhoto != null) {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(
+ new File(pendingPhoto));
+ });
+ }
+ mCurrentState = savedInstanceState.getInt(KEY_CURRENT_STATE);
+ if (savedInstanceState.containsKey(KEY_IS_ADMIN)) {
+ mIsAdmin = savedInstanceState.getBoolean(KEY_IS_ADMIN);
+ }
+ mSavedName = savedInstanceState.getString(KEY_SAVED_NAME);
+ mWaitingForActivityResult = savedInstanceState.getBoolean(KEY_AWAITING_RESULT, false);
+ }
+
+ /**
+ * Notifies that the containing activity or fragment is saving its state for later use.
+ */
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ if (mUserCreationDialog != null && mEditUserPhotoController != null) {
+ // Bitmap cannot be stored into bundle because it may exceed parcel limit
+ // Store it in a temporary file instead
+ ThreadUtils.postOnBackgroundThread(() -> {
+ File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
+ if (file != null) {
+ savedInstanceState.putString(KEY_SAVED_PHOTO, file.getPath());
+ }
+ });
+ }
+ if (mIsAdmin != null) {
+ savedInstanceState.putBoolean(KEY_IS_ADMIN, Boolean.TRUE.equals(mIsAdmin));
+ }
+ savedInstanceState.putString(KEY_SAVED_NAME, mUserNameView.getText().toString().trim());
+ savedInstanceState.putInt(KEY_CURRENT_STATE, mCurrentState);
+ savedInstanceState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
+ }
+
+ /**
+ * Notifies that an activity has started.
+ */
+ public void startingActivityForResult() {
+ mWaitingForActivityResult = true;
+ }
+
+ /**
+ * Notifies that the result from activity has been received.
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mWaitingForActivityResult = false;
+ if (mEditUserPhotoController != null) {
+ mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ /**
+ * Creates an add user dialog with option to set the user's name and photo and choose their
+ * admin status.
+ */
+ public Dialog createDialog(Activity activity,
+ ActivityStarter activityStarter, boolean isMultipleAdminEnabled,
+ NewUserData successCallback, Runnable cancelCallback) {
+ mActivity = activity;
+ mCustomDialogHelper = new CustomDialogHelper(activity);
+ mSuccessCallback = successCallback;
+ mActivityStarter = activityStarter;
+ addCustomViews(isMultipleAdminEnabled);
+ mUserCreationDialog = mCustomDialogHelper.getDialog();
+ updateLayout();
+ mUserCreationDialog.setOnDismissListener(view -> {
+ cancelCallback.run();
+ clear();
+ });
+ mUserCreationDialog.setCanceledOnTouchOutside(true);
+ return mUserCreationDialog;
+ }
+
+ private void addCustomViews(boolean isMultipleAdminEnabled) {
+ addGrantAdminView();
+ addUserInfoEditView();
+ mCustomDialogHelper.setPositiveButton(R.string.next, view -> {
+ mCurrentState++;
+ if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) {
+ mCurrentState++;
+ }
+ updateLayout();
+ });
+ mCustomDialogHelper.setNegativeButton(R.string.back, view -> {
+ mCurrentState--;
+ if (mCurrentState == GRANT_ADMIN_DIALOG && !isMultipleAdminEnabled) {
+ mCurrentState--;
+ }
+ updateLayout();
+ });
+ return;
+ }
+
+ private void updateLayout() {
+ switch (mCurrentState) {
+ case INITIAL_DIALOG:
+ mEditUserInfoView.setVisibility(View.GONE);
+ mGrantAdminView.setVisibility(View.GONE);
+ final SharedPreferences preferences = mActivity.getPreferences(
+ Context.MODE_PRIVATE);
+ final boolean longMessageDisplayed = preferences.getBoolean(
+ KEY_ADD_USER_LONG_MESSAGE_DISPLAYED, false);
+ final int messageResId = longMessageDisplayed
+ ? R.string.user_add_user_message_short
+ : R.string.user_add_user_message_long;
+ if (!longMessageDisplayed) {
+ preferences.edit().putBoolean(
+ KEY_ADD_USER_LONG_MESSAGE_DISPLAYED,
+ true).apply();
+ }
+ Drawable icon = mActivity.getDrawable(R.drawable.ic_person_add);
+ mCustomDialogHelper.setVisibility(mCustomDialogHelper.ICON, true)
+ .setVisibility(mCustomDialogHelper.TITLE, true)
+ .setVisibility(mCustomDialogHelper.MESSAGE, true)
+ .setIcon(icon)
+ .setButtonEnabled(true)
+ .setTitle(R.string.user_add_user_title)
+ .setMessage(messageResId)
+ .setNegativeButtonText(R.string.cancel)
+ .setPositiveButtonText(R.string.next);
+ break;
+ case GRANT_ADMIN_DIALOG:
+ mEditUserInfoView.setVisibility(View.GONE);
+ mGrantAdminView.setVisibility(View.VISIBLE);
+ mCustomDialogHelper
+ .setVisibility(mCustomDialogHelper.ICON, true)
+ .setVisibility(mCustomDialogHelper.TITLE, true)
+ .setVisibility(mCustomDialogHelper.MESSAGE, true)
+ .setIcon(mActivity.getDrawable(R.drawable.ic_admin_panel_settings))
+ .setTitle(R.string.user_grant_admin_title)
+ .setMessage(R.string.user_grant_admin_message)
+ .setNegativeButtonText(R.string.back)
+ .setPositiveButtonText(R.string.next);
+ if (mIsAdmin == null) {
+ mCustomDialogHelper.setButtonEnabled(false);
+ }
+ break;
+ case EDIT_NAME_DIALOG:
+ mCustomDialogHelper
+ .setVisibility(mCustomDialogHelper.ICON, false)
+ .setVisibility(mCustomDialogHelper.TITLE, false)
+ .setVisibility(mCustomDialogHelper.MESSAGE, false)
+ .setNegativeButtonText(R.string.back)
+ .setPositiveButtonText(R.string.done);
+ mEditUserInfoView.setVisibility(View.VISIBLE);
+ mGrantAdminView.setVisibility(View.GONE);
+ break;
+ case CREATE_USER_AND_CLOSE:
+ Drawable newUserIcon = mEditUserPhotoController != null
+ ? mEditUserPhotoController.getNewUserPhotoDrawable()
+ : null;
+
+ String newName = mUserNameView.getText().toString().trim();
+ String defaultName = mActivity.getString(R.string.user_new_user_name);
+ String userName = !newName.isEmpty() ? newName : defaultName;
+
+ if (mSuccessCallback != null) {
+ mSuccessCallback.onSuccess(userName, newUserIcon,
+ Boolean.TRUE.equals(mIsAdmin));
+ }
+ mCustomDialogHelper.getDialog().dismiss();
+ clear();
+ break;
+ case EXIT_DIALOG:
+ mCustomDialogHelper.getDialog().dismiss();
+ break;
+ default:
+ if (mCurrentState < EXIT_DIALOG) {
+ mCurrentState = EXIT_DIALOG;
+ updateLayout();
+ } else {
+ mCurrentState = CREATE_USER_AND_CLOSE;
+ updateLayout();
+ }
+ break;
+ }
+ }
+
+ private Drawable getUserIcon(Drawable defaultUserIcon) {
+ if (mSavedPhoto != null) {
+ mSavedDrawable = CircleFramedDrawable.getInstance(mActivity, mSavedPhoto);
+ return mSavedDrawable;
+ }
+ return defaultUserIcon;
+ }
+
+ private void addUserInfoEditView() {
+ mEditUserInfoView = View.inflate(mActivity, R.layout.edit_user_info_dialog_content, null);
+ mCustomDialogHelper.addCustomView(mEditUserInfoView);
+ setUserName();
+ ImageView userPhotoView = mEditUserInfoView.findViewById(R.id.user_photo);
+
+ // if oldUserIcon param is null then we use a default gray user icon
+ Drawable defaultUserIcon = UserIcons.getDefaultUserIcon(
+ mActivity.getResources(), UserHandle.USER_NULL, false);
+ // in case a new photo was selected and the activity got recreated we have to load the image
+ Drawable userIcon = getUserIcon(defaultUserIcon);
+ userPhotoView.setImageDrawable(userIcon);
+
+ if (isChangePhotoRestrictedByBase(mActivity)) {
+ // some users can't change their photos so we need to remove the suggestive icon
+ mEditUserInfoView.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
+ } else {
+ RestrictedLockUtils.EnforcedAdmin adminRestriction =
+ getChangePhotoAdminRestriction(mActivity);
+ if (adminRestriction != null) {
+ userPhotoView.setOnClickListener(view ->
+ RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
+ mActivity, adminRestriction));
+ } else {
+ mEditUserPhotoController = createEditUserPhotoController(userPhotoView);
+ }
+ }
+ }
+
+ private void setUserName() {
+ mUserNameView = mEditUserInfoView.findViewById(R.id.user_name);
+ if (mSavedName == null) {
+ mUserNameView.setText(R.string.user_new_user_name);
+ } else {
+ mUserNameView.setText(mSavedName);
+ }
+ }
+
+ private void addGrantAdminView() {
+ mGrantAdminView = View.inflate(mActivity, R.layout.grant_admin_dialog_content, null);
+ mCustomDialogHelper.addCustomView(mGrantAdminView);
+ RadioGroup radioGroup = mGrantAdminView.findViewById(R.id.choose_admin);
+ radioGroup.setOnCheckedChangeListener((group, checkedId) -> {
+ mCustomDialogHelper.setButtonEnabled(true);
+ mIsAdmin = checkedId == R.id.grant_admin_yes;
+ }
+ );
+ if (Boolean.TRUE.equals(mIsAdmin)) {
+ RadioButton button = radioGroup.findViewById(R.id.grant_admin_yes);
+ button.setChecked(true);
+ } else if (Boolean.FALSE.equals(mIsAdmin)) {
+ RadioButton button = radioGroup.findViewById(R.id.grant_admin_no);
+ button.setChecked(true);
+ }
+ }
+
+ @VisibleForTesting
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return RestrictedLockUtilsInternal.hasBaseUserRestriction(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+ return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+ context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
+ }
+
+ @VisibleForTesting
+ EditUserPhotoController createEditUserPhotoController(ImageView userPhotoView) {
+ return new EditUserPhotoController(mActivity, mActivityStarter, userPhotoView,
+ mSavedPhoto, mSavedDrawable, mFileAuthority);
+ }
+
+ public boolean isActive() {
+ return mCustomDialogHelper != null && mCustomDialogHelper.getDialog() != null;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java
new file mode 100644
index 0000000..3d18b59
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/NewUserData.java
@@ -0,0 +1,34 @@
+/*
+ * 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.settingslib.users;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Defines a callback when a new user data is filled out.
+ */
+public interface NewUserData {
+
+ /**
+ * Consumes data relevant to new user that needs to be created.
+ * @param userName New user name.
+ * @param userImage New user icon.
+ * @param isNewUserAdmin A boolean that indicated whether new user has admin status.
+ */
+ void onSuccess(String userName, Drawable userImage, Boolean isNewUserAdmin);
+
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
new file mode 100644
index 0000000..9b41bc4
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java
@@ -0,0 +1,345 @@
+/*
+ * 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.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Parcel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class CsipDeviceManagerTest {
+ private final static String DEVICE_NAME_1 = "TestName_1";
+ private final static String DEVICE_NAME_2 = "TestName_2";
+ private final static String DEVICE_NAME_3 = "TestName_3";
+ private final static String DEVICE_ALIAS_1 = "TestAlias_1";
+ private final static String DEVICE_ALIAS_2 = "TestAlias_2";
+ private final static String DEVICE_ALIAS_3 = "TestAlias_3";
+ private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
+ private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
+ private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
+ private final static int GROUP1 = 1;
+ private final BluetoothClass DEVICE_CLASS_1 =
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+ private final BluetoothClass DEVICE_CLASS_2 =
+ createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+
+ @Mock
+ private LocalBluetoothManager mLocalBluetoothManager;
+ @Mock
+ private LocalBluetoothProfileManager mLocalProfileManager;
+ @Mock
+ private BluetoothEventManager mBluetoothEventManager;
+ @Mock
+ private BluetoothDevice mDevice1;
+ @Mock
+ private BluetoothDevice mDevice2;
+ @Mock
+ private BluetoothDevice mDevice3;
+ @Mock
+ private HeadsetProfile mHfpProfile;
+ @Mock
+ private A2dpProfile mA2dpProfile;
+ @Mock
+ private LeAudioProfile mLeAudioProfile;
+
+ private CachedBluetoothDevice mCachedDevice1;
+ private CachedBluetoothDevice mCachedDevice2;
+ private CachedBluetoothDevice mCachedDevice3;
+ private CachedBluetoothDeviceManager mCachedDeviceManager;
+ private CsipDeviceManager mCsipDeviceManager;
+ private Context mContext;
+ private List<CachedBluetoothDevice> mCachedDevices = new ArrayList<CachedBluetoothDevice>();
+
+
+ private BluetoothClass createBtClass(int deviceClass) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(deviceClass);
+ p.setDataPosition(0); // reset position of parcel before passing to constructor
+
+ BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
+ p.recycle();
+ return bluetoothClass;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
+ when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
+ when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3);
+ when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
+ when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
+ when(mDevice3.getName()).thenReturn(DEVICE_NAME_3);
+ when(mDevice1.getAlias()).thenReturn(DEVICE_ALIAS_1);
+ when(mDevice2.getAlias()).thenReturn(DEVICE_ALIAS_2);
+ when(mDevice3.getAlias()).thenReturn(DEVICE_ALIAS_3);
+ when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1);
+ when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
+ when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
+ when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
+ when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+ when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
+ when(mLocalProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile);
+
+ when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(null);
+ mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager);
+
+ mCachedDevices = mCachedDeviceManager.mCachedDevices;
+ mCsipDeviceManager = mCachedDeviceManager.mCsipDeviceManager;
+
+ // Setup the default for testing
+ mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
+ mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
+ mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3));
+
+ mCachedDevice1.setGroupId(GROUP1);
+ mCachedDevice2.setGroupId(GROUP1);
+ mCachedDevice1.addMemberDevice(mCachedDevice2);
+ mCachedDevices.add(mCachedDevice1);
+ mCachedDevices.add(mCachedDevice3);
+
+ List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
+ profiles.add(mHfpProfile);
+ profiles.add(mA2dpProfile);
+ profiles.add(mLeAudioProfile);
+ when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice1.isConnected()).thenReturn(true);
+
+ profiles.clear();
+ profiles.add(mLeAudioProfile);
+ when(mCachedDevice2.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice2.isConnected()).thenReturn(true);
+
+ profiles.clear();
+ profiles.add(mHfpProfile);
+ profiles.add(mA2dpProfile);
+ when(mCachedDevice3.getConnectableProfiles()).thenReturn(profiles);
+ when(mCachedDevice3.isConnected()).thenReturn(true);
+
+ }
+
+ @Test
+ public void onProfileConnectionStateChangedIfProcessed_profileIsConnecting_returnOff() {
+ assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+ BluetoothProfile.STATE_CONNECTING)).isFalse();
+ }
+
+ @Test
+ public void onProfileConnectionStateChangedIfProcessed_profileIsDisconnecting_returnOff() {
+ assertThat(mCsipDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+ BluetoothProfile.STATE_DISCONNECTING)).isFalse();
+ }
+
+ @Test
+ public void updateRelationshipOfGroupDevices_invalidGroupId_returnOff() {
+ assertThat(mCsipDeviceManager.updateRelationshipOfGroupDevices(
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID)).isFalse();
+ }
+
+ @Test
+ public void getGroupDevicesFromAllOfDevicesList_invalidGroupId_returnEmpty() {
+ assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID).isEmpty()).isTrue();
+ }
+
+ @Test
+ public void getGroupDevicesFromAllOfDevicesList_validGroupId_returnGroupDevices() {
+ List<CachedBluetoothDevice> expectedList = new ArrayList<>();
+ expectedList.add(mCachedDevice1);
+ expectedList.add(mCachedDevice2);
+
+ assertThat(mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1))
+ .isEqualTo(expectedList);
+ }
+
+ @Test
+ public void getPreferredMainDevice_dualModeDevice_returnDualModeDevice() {
+ CachedBluetoothDevice expectedDevice = mCachedDevice1;
+
+ assertThat(
+ mCsipDeviceManager.getPreferredMainDevice(GROUP1,
+ mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
+ .isEqualTo(expectedDevice);
+ }
+
+ @Test
+ public void getPreferredMainDevice_noConnectedDualModeDevice_returnLeadDevice() {
+ when(mCachedDevice1.isConnected()).thenReturn(false);
+ when(mLeAudioProfile.getConnectedGroupLeadDevice(anyInt())).thenReturn(mDevice2);
+ CachedBluetoothDevice expectedDevice = mCachedDevice2;
+
+ assertThat(
+ mCsipDeviceManager.getPreferredMainDevice(GROUP1,
+ mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
+ .isEqualTo(expectedDevice);
+ }
+
+ @Test
+ public void getPreferredMainDevice_noConnectedDualModeDeviceNoLeadDevice_returnConnectedOne() {
+ when(mCachedDevice1.isConnected()).thenReturn(false);
+ when(mCachedDevice2.isConnected()).thenReturn(true);
+ CachedBluetoothDevice expectedDevice = mCachedDevice2;
+
+ assertThat(
+ mCsipDeviceManager.getPreferredMainDevice(GROUP1,
+ mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
+ .isEqualTo(expectedDevice);
+ }
+
+ @Test
+ public void getPreferredMainDevice_noConnectedDevice_returnDualModeDevice() {
+ when(mCachedDevice1.isConnected()).thenReturn(false);
+ when(mCachedDevice2.isConnected()).thenReturn(false);
+ CachedBluetoothDevice expectedDevice = mCachedDevice1;
+
+ assertThat(
+ mCsipDeviceManager.getPreferredMainDevice(GROUP1,
+ mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
+ .isEqualTo(expectedDevice);
+ }
+
+ @Test
+ public void getPreferredMainDevice_noConnectedDeviceNoDualMode_returnFirstOneDevice() {
+ when(mCachedDevice1.isConnected()).thenReturn(false);
+ when(mCachedDevice2.isConnected()).thenReturn(false);
+ List<LocalBluetoothProfile> profiles = new ArrayList<LocalBluetoothProfile>();
+ profiles.add(mLeAudioProfile);
+ when(mCachedDevice1.getConnectableProfiles()).thenReturn(profiles);
+ CachedBluetoothDevice expectedDevice = mCachedDevice1;
+
+ assertThat(
+ mCsipDeviceManager.getPreferredMainDevice(GROUP1,
+ mCsipDeviceManager.getGroupDevicesFromAllOfDevicesList(GROUP1)))
+ .isEqualTo(expectedDevice);
+ }
+
+ @Test
+ public void addMemberDevicesIntoMainDevice_noPreferredDevice_returnFalseAndNoChangeList() {
+ CachedBluetoothDevice preferredDevice = null;
+ List<CachedBluetoothDevice> expectedList = new ArrayList<>();
+ for (CachedBluetoothDevice item : mCachedDevices) {
+ expectedList.add(item);
+ }
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isFalse();
+ for (CachedBluetoothDevice expectedItem : expectedList) {
+ assertThat(mCachedDevices.contains(expectedItem)).isTrue();
+ }
+ }
+
+ @Test
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndNoOtherInList_noChangeList()
+ {
+ // Condition: The preferredDevice is main and no other main device in top list
+ // Expected Result: return false and the list is no changed
+ CachedBluetoothDevice preferredDevice = mCachedDevice1;
+ List<CachedBluetoothDevice> expectedList = new ArrayList<>();
+ for (CachedBluetoothDevice item : mCachedDevices) {
+ expectedList.add(item);
+ }
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isFalse();
+ for (CachedBluetoothDevice expectedItem : expectedList) {
+ assertThat(mCachedDevices.contains(expectedItem)).isTrue();
+ }
+ }
+
+ @Test
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMainAndTwoMain_returnTrue() {
+ // Condition: The preferredDevice is main and there is another main device in top list
+ // Expected Result: return true and there is the preferredDevice in top list
+ CachedBluetoothDevice preferredDevice = mCachedDevice1;
+ mCachedDevice1.getMemberDevice().clear();
+ mCachedDevices.clear();
+ mCachedDevices.add(preferredDevice);
+ mCachedDevices.add(mCachedDevice2);
+ mCachedDevices.add(mCachedDevice3);
+
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isTrue();
+ assertThat(mCachedDevices.contains(preferredDevice)).isTrue();
+ assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+ assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
+ assertThat(preferredDevice.getMemberDevice()).contains(mCachedDevice2);
+ }
+
+ @Test
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMember_returnTrue() {
+ // Condition: The preferredDevice is member
+ // Expected Result: return true and there is the preferredDevice in top list
+ CachedBluetoothDevice preferredDevice = mCachedDevice2;
+ BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isTrue();
+ // expected main is mCachedDevice1 which is the main of preferredDevice, since system
+ // switch the relationship between preferredDevice and the main of preferredDevice
+ assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue();
+ assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+ assertThat(mCachedDevices.contains(mCachedDevice3)).isTrue();
+ assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
+ assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
+ }
+
+ @Test
+ public void addMemberDevicesIntoMainDevice_preferredDeviceIsMemberAndTwoMain_returnTrue() {
+ // Condition: The preferredDevice is member and there are two main device in top list
+ // Expected Result: return true and there is the preferredDevice in top list
+ CachedBluetoothDevice preferredDevice = mCachedDevice2;
+ BluetoothDevice expectedMainBluetoothDevice = preferredDevice.getDevice();
+ mCachedDevice3.setGroupId(GROUP1);
+
+ assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
+ .isTrue();
+ // expected main is mCachedDevice1 which is the main of preferredDevice, since system
+ // switch the relationship between preferredDevice and the main of preferredDevice
+ assertThat(mCachedDevices.contains(mCachedDevice1)).isTrue();
+ assertThat(mCachedDevices.contains(mCachedDevice2)).isFalse();
+ assertThat(mCachedDevices.contains(mCachedDevice3)).isFalse();
+ assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice2);
+ assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
+ assertThat(mCachedDevice1.getMemberDevice()).contains(mCachedDevice3);
+ assertThat(mCachedDevice1.getDevice()).isEqualTo(expectedMainBluetoothDevice);
+ }
+}
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/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
new file mode 100644
index 0000000..e989ed2
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.settingslib.users;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.RadioButton;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settingslib.R;
+import com.android.settingslib.RestrictedLockUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.android.controller.ActivityController;
+
+@RunWith(RobolectricTestRunner.class)
+public class CreateUserDialogControllerTest {
+
+ @Mock
+ private ActivityStarter mActivityStarter;
+
+ private boolean mPhotoRestrictedByBase;
+ private Activity mActivity;
+ private TestCreateUserDialogController mUnderTest;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mActivity = spy(ActivityController.of(new FragmentActivity()).get());
+ mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+ mUnderTest = new TestCreateUserDialogController();
+ mPhotoRestrictedByBase = false;
+ }
+
+ @Test
+ public void positiveButton_grantAdminStage_noValue_OkButtonShouldBeDisabled() {
+ Runnable cancelCallback = mock(Runnable.class);
+
+ final AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, null,
+ cancelCallback);
+ dialog.show();
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(true);
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(false);
+ ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(true);
+ dialog.dismiss();
+ }
+
+ @Test
+ public void positiveButton_MultipleAdminDisabled_shouldSkipGrantAdminStage() {
+ Runnable cancelCallback = mock(Runnable.class);
+
+ final AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, false, null,
+ cancelCallback);
+ dialog.show();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility()).isEqualTo(View.GONE);
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(true);
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility()).isEqualTo(View.GONE);
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(true);
+ Button back = dialog.findViewById(R.id.button_cancel);
+ back.performClick();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility()).isEqualTo(View.GONE);
+ dialog.dismiss();
+ }
+
+ @Test
+ public void editUserInfoController_shouldOnlyBeVisibleOnLastStage() {
+ Runnable cancelCallback = mock(Runnable.class);
+ final AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, null,
+ cancelCallback);
+ dialog.show();
+ assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
+ assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility()).isEqualTo(View.GONE);
+ next.performClick();
+ assertThat(dialog.findViewById(R.id.user_info_scroll).getVisibility())
+ .isEqualTo(View.VISIBLE);
+ dialog.dismiss();
+ }
+
+ @Test
+ public void positiveButton_MultipleAdminEnabled_shouldShowGrantAdminStage() {
+ Runnable cancelCallback = mock(Runnable.class);
+
+ final AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, null,
+ cancelCallback);
+ dialog.show();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility()).isEqualTo(View.GONE);
+ assertThat(dialog.findViewById(R.id.button_ok).isEnabled()).isEqualTo(true);
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility())
+ .isEqualTo(View.VISIBLE);
+ ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
+ next.performClick();
+ assertThat(dialog.findViewById(R.id.grant_admin_view).getVisibility()).isEqualTo(View.GONE);
+ dialog.dismiss();
+ }
+
+ @Test
+ public void cancelCallback_isCalled_whenCancelled() {
+ NewUserData successCallback = mock(NewUserData.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, successCallback,
+ cancelCallback);
+ dialog.show();
+ dialog.cancel();
+ verifyNoInteractions(successCallback);
+ verify(cancelCallback, times(1))
+ .run();
+ }
+
+ @Test
+ public void cancelCallback_isCalled_whenNegativeButtonClickedOnFirstStage() {
+ NewUserData successCallback = mock(NewUserData.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, successCallback,
+ cancelCallback);
+ dialog.show();
+ Button back = dialog.findViewById(R.id.button_cancel);
+ back.performClick();
+ verifyNoInteractions(successCallback);
+ verify(cancelCallback, times(1))
+ .run();
+ }
+
+ @Test
+ public void cancelCallback_isNotCalled_whenNegativeButtonClickedOnSecondStage() {
+ NewUserData successCallback = mock(NewUserData.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, successCallback,
+ cancelCallback);
+ dialog.show();
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ Button back = dialog.findViewById(R.id.button_cancel);
+ back.performClick();
+ verifyNoInteractions(successCallback);
+ verifyNoInteractions(cancelCallback);
+ dialog.dismiss();
+ }
+
+ @Test
+ public void successCallback_isCalled_setNameAndAdminStatus() {
+ NewUserData successCallback = mock(NewUserData.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, true, successCallback,
+ cancelCallback);
+ // No photo chosen
+ when(mUnderTest.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
+ dialog.show();
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ ((RadioButton) dialog.findViewById(R.id.grant_admin_yes)).setChecked(true);
+ next.performClick();
+ String expectedNewName = "Test";
+ EditText editText = dialog.findViewById(R.id.user_name);
+ editText.setText(expectedNewName);
+ next.performClick();
+ verify(successCallback, times(1))
+ .onSuccess(expectedNewName, null, true);
+ }
+
+ @Test
+ public void successCallback_isCalled_setName_MultipleAdminDisabled() {
+ NewUserData successCallback = mock(NewUserData.class);
+ Runnable cancelCallback = mock(Runnable.class);
+
+ AlertDialog dialog = (AlertDialog) mUnderTest.createDialog(mActivity,
+ mActivityStarter, false, successCallback,
+ cancelCallback);
+ // No photo chosen
+ when(mUnderTest.getPhotoController().getNewUserPhotoDrawable()).thenReturn(null);
+ dialog.show();
+ Button next = dialog.findViewById(R.id.button_ok);
+ next.performClick();
+ String expectedNewName = "Test";
+ EditText editText = dialog.findViewById(R.id.user_name);
+ editText.setText(expectedNewName);
+ next.performClick();
+ verify(successCallback, times(1))
+ .onSuccess(expectedNewName, null, false);
+ }
+
+ private class TestCreateUserDialogController extends CreateUserDialogController {
+ private EditUserPhotoController mPhotoController;
+
+ TestCreateUserDialogController() {
+ super("file_authority");
+ }
+
+ private EditUserPhotoController getPhotoController() {
+ return mPhotoController;
+ }
+
+ @Override
+ EditUserPhotoController createEditUserPhotoController(ImageView userPhotoView) {
+ mPhotoController = mock(EditUserPhotoController.class, Answers.RETURNS_DEEP_STUBS);
+ return mPhotoController;
+ }
+ @Override
+ RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
+ return null;
+ }
+
+ @Override
+ boolean isChangePhotoRestrictedByBase(Context context) {
+ return mPhotoRestrictedByBase;
+ }
+ }
+}
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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 8b3fd41..43f98c3 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -837,6 +837,8 @@
<uses-permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE" />
<!-- Permission required for GTS test - GtsCredentialsTestCases -->
<uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
+ <!-- Permission required for CTS test IntentRedirectionTest -->
+ <uses-permission android:name="android.permission.QUERY_CLONED_APPS" />
<application
android:label="@string/app_label"
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/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt
new file mode 100644
index 0000000..18c9513
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/scene/shared/page/SceneModule.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.shared.page
+
+import com.android.systemui.scene.shared.model.Scene
+import dagger.Module
+import dagger.multibindings.Multibinds
+
+@Module
+interface SceneModule {
+ @Multibinds fun scenes(): Set<Scene>
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
new file mode 100644
index 0000000..530706e
--- /dev/null
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.composable
+
+import com.android.systemui.bouncer.ui.composable.BouncerScene
+import com.android.systemui.keyguard.ui.composable.LockScreenScene
+import com.android.systemui.qs.ui.composable.QuickSettingsScene
+import com.android.systemui.scene.shared.model.Scene
+import com.android.systemui.shade.ui.composable.ShadeScene
+import dagger.Module
+import dagger.Provides
+
+@Module
+object SceneModule {
+ @Provides
+ fun scenes(
+ bouncer: BouncerScene,
+ gone: GoneScene,
+ lockScreen: LockScreenScene,
+ qs: QuickSettingsScene,
+ shade: ShadeScene,
+ ): Set<Scene> {
+ return setOf(
+ bouncer,
+ gone,
+ lockScreen,
+ qs,
+ shade,
+ )
+ }
+}
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
new file mode 100644
index 0000000..6f6d0f9
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.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.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.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
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
+@SysUISingleton
+class BouncerScene
+@Inject
+constructor(
+ private val viewModel: BouncerViewModel,
+) : ComposableScene {
+ override val key = SceneKey.Bouncer
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Back to SceneModel(SceneKey.Lockscreen),
+ )
+ )
+ .asStateFlow()
+
+ @Composable override fun Content(modifier: Modifier) = BouncerScene(viewModel, modifier)
+}
+
+@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/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
new file mode 100644
index 0000000..ab7bc26
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.ui.composable
+
+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.material3.Button
+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.unit.dp
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** The lock screen scene shows when the device is locked. */
+@SysUISingleton
+class LockscreenScene
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val viewModel: LockscreenSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.Lockscreen
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ viewModel.upDestinationSceneKey
+ .map { pageKey -> destinationScenes(up = pageKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
+ )
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ LockscreenScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+
+ private fun destinationScenes(
+ up: SceneKey,
+ ): Map<UserAction, SceneModel> {
+ return mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(up),
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
+ )
+ }
+}
+
+@Composable
+private fun LockscreenScene(
+ viewModel: LockscreenSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280879610): implement the real UI.
+
+ val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState()
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Lockscreen", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) }
+
+ Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
new file mode 100644
index 0000000..130395a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.qs.ui.composable
+
+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.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
+@SysUISingleton
+class QuickSettingsScene
+@Inject
+constructor(
+ private val viewModel: QuickSettingsSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.QuickSettings
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
+ )
+ )
+ .asStateFlow()
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ QuickSettingsScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+}
+
+@Composable
+private fun QuickSettingsScene(
+ viewModel: QuickSettingsSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280887232): implement the real UI.
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
new file mode 100644
index 0000000..a213666
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.systemui.scene.shared.model.Scene
+
+/** Compose-capable extension of [Scene]. */
+interface ComposableScene : Scene {
+ @Composable fun Content(modifier: Modifier)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
new file mode 100644
index 0000000..0070552
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.ui.composable
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
+ * content from the scene framework.
+ */
+@SysUISingleton
+class GoneScene @Inject constructor() : ComposableScene {
+ override val key = SceneKey.Gone
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
+ )
+ )
+ .asStateFlow()
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ /*
+ * TODO(b/279501596): once we start testing with the real Content Dynamics Framework,
+ * replace this with an error to make sure it doesn't get rendered.
+ */
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Gone", style = MaterialTheme.typography.headlineMedium)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
new file mode 100644
index 0000000..f8a73d5
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.scene.ui.composable
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.ExperimentalAnimationApi
+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.fillMaxSize
+import androidx.compose.material3.Button
+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.unit.dp
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import java.util.Locale
+
+/**
+ * Renders a container of a collection of "scenes" that the user can switch between using certain
+ * user actions (for instance, swiping up and down) or that can be switched automatically based on
+ * application business logic in response to certain events (for example, the device unlocking).
+ *
+ * It's possible for the application to host several such scene containers, the configuration system
+ * allows configuring each container with its own set of scenes. Scenes can be present in multiple
+ * containers.
+ *
+ * @param viewModel The UI state holder for this container.
+ * @param sceneByKey Mapping of [ComposableScene] by [SceneKey], ordered by z-order such that the
+ * last scene is rendered on top of all other scenes. It's critical that this map contains exactly
+ * and only the scenes on this container. In other words: (a) there should be no scene in this map
+ * that is not in the configuration for this container and (b) all scenes in the configuration
+ * must have entries in this map.
+ * @param modifier A modifier.
+ */
+@Composable
+fun SceneContainer(
+ viewModel: SceneContainerViewModel,
+ sceneByKey: Map<SceneKey, ComposableScene>,
+ modifier: Modifier = Modifier,
+) {
+ val currentScene: SceneModel by viewModel.currentScene.collectAsState()
+
+ AnimatedContent(
+ targetState = currentScene.key,
+ label = "scene container",
+ modifier = modifier,
+ ) { currentSceneKey ->
+ sceneByKey.forEach { (key, composableScene) ->
+ if (key == currentSceneKey) {
+ Scene(
+ scene = composableScene,
+ onSceneChanged = viewModel::setCurrentScene,
+ modifier = Modifier.fillMaxSize(),
+ )
+ }
+ }
+ }
+}
+
+/** Renders the given [ComposableScene]. */
+@Composable
+private fun Scene(
+ scene: ComposableScene,
+ onSceneChanged: (SceneModel) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress.
+ Box(modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center),
+ ) {
+ scene.Content(
+ modifier = Modifier,
+ )
+
+ val destinationScenes: Map<UserAction, SceneModel> by
+ scene.destinationScenes().collectAsState()
+ val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
+ val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
+ val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
+ val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)]
+ val backDestinationScene = destinationScenes[UserAction.Back]
+
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged)
+ DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged)
+ }
+
+ if (backDestinationScene != null) {
+ BackHandler { onSceneChanged.invoke(backDestinationScene) }
+ }
+ }
+ }
+}
+
+@Composable
+private fun DirectionalButton(
+ direction: Direction,
+ destinationScene: SceneModel?,
+ onSceneChanged: (SceneModel) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ Button(
+ onClick = { destinationScene?.let { onSceneChanged.invoke(it) } },
+ enabled = destinationScene != null,
+ modifier = modifier,
+ ) {
+ Text(direction.name.lowercase(Locale.getDefault()))
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
new file mode 100644
index 0000000..5a09204
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.composable
+
+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.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.ui.composable.ComposableScene
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
+@SysUISingleton
+class ShadeScene
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val viewModel: ShadeSceneViewModel,
+) : ComposableScene {
+ override val key = SceneKey.Shade
+
+ override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+ viewModel.upDestinationSceneKey
+ .map { sceneKey -> destinationScenes(up = sceneKey) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
+ )
+
+ @Composable
+ override fun Content(
+ modifier: Modifier,
+ ) {
+ ShadeScene(
+ viewModel = viewModel,
+ modifier = modifier,
+ )
+ }
+
+ private fun destinationScenes(
+ up: SceneKey,
+ ): Map<UserAction, SceneModel> {
+ return mapOf(
+ UserAction.Swipe(Direction.UP) to SceneModel(up),
+ UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.QuickSettings),
+ )
+ }
+}
+
+@Composable
+private fun ShadeScene(
+ viewModel: ShadeSceneViewModel,
+ modifier: Modifier = Modifier,
+) {
+ // TODO(b/280887022): implement the real UI.
+
+ Box(modifier = modifier) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.align(Alignment.Center)
+ ) {
+ Text("Shade", style = MaterialTheme.typography.headlineMedium)
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Button(
+ onClick = { viewModel.onContentClicked() },
+ ) {
+ Text("Open some content")
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
index af1a11f..2007e76 100644
--- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt
@@ -271,11 +271,13 @@
}
private fun echoToSystrace(message: LogMessage, strMessage: String) {
- Trace.instantForTrack(
- Trace.TRACE_TAG_APP,
- "UI Events",
- "$name - ${message.level.shortString} ${message.tag}: $strMessage"
- )
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_APP,
+ "UI Events",
+ "$name - ${message.level.shortString} ${message.tag}: $strMessage"
+ )
+ }
}
private fun echoToLogcat(message: LogMessage, strMessage: String) {
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_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 8bb7877..371670c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -39,7 +39,8 @@
android:ellipsize="marquee"
android:visibility="gone"
android:gravity="center"
- androidprv:allCaps="@bool/kg_use_all_caps" />
+ androidprv:allCaps="@bool/kg_use_all_caps"
+ androidprv:debugLocation="Emergency" />
<com.android.keyguard.EmergencyButton
android:id="@+id/emergency_call_button"
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/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index e9acf3f..a3a7135 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,55 +59,44 @@
</LinearLayout>
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="bottom"
- android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+ <com.android.systemui.animation.view.LaunchableImageView
+ android:id="@+id/start_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="start|bottom"
+ android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
- android:gravity="bottom"
- >
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+ android:visibility="invisible" />
- <com.android.systemui.animation.view.LaunchableImageView
- android:id="@+id/start_button"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:scaleType="fitCenter"
- android:padding="@dimen/keyguard_affordance_fixed_padding"
- android:tint="?android:attr/textColorPrimary"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:visibility="invisible" />
+ <com.android.systemui.animation.view.LaunchableImageView
+ android:id="@+id/end_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:layout_gravity="end|bottom"
+ android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+ android:visibility="invisible" />
- <FrameLayout
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:paddingHorizontal="24dp"
- >
- <include
- android:id="@+id/keyguard_settings_button"
- layout="@layout/keyguard_settings_popup_menu"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:visibility="gone"
- />
- </FrameLayout>
-
- <com.android.systemui.animation.view.LaunchableImageView
- android:id="@+id/end_button"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:scaleType="fitCenter"
- android:padding="@dimen/keyguard_affordance_fixed_padding"
- android:tint="?android:attr/textColorPrimary"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:visibility="invisible" />
-
- </LinearLayout>
+ <include
+ android:id="@+id/keyguard_settings_button"
+ layout="@layout/keyguard_settings_popup_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|center"
+ android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+ android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
+ android:visibility="gone"
+ />
<FrameLayout
android:id="@+id/overlay_container"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index 8b85940..64c4eff 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -78,6 +78,7 @@
android:textColor="?attr/wallpaperTextColorSecondary"
android:singleLine="true"
systemui:showMissingSim="true"
- systemui:showAirplaneMode="true" />
+ systemui:showAirplaneMode="true"
+ systemui:debugLocation="Keyguard" />
</com.android.systemui.statusbar.phone.KeyguardStatusBarView>
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..2a72dfb 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.14","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":[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":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.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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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":[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/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 8d8fdf0..bd86e51 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -146,6 +146,7 @@
<attr name="allCaps" format="boolean" />
<attr name="showMissingSim" format="boolean" />
<attr name="showAirplaneMode" format="boolean" />
+ <attr name="debugLocation" format="string" />
</declare-styleable>
<declare-styleable name="IlluminationDrawable">
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 a2e3ff4b..0a7633d 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 cbc73fa..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>
@@ -3101,17 +3103,20 @@
<!-- Switch to work profile dialer app for placing a call dialog. -->
<!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
- <string name="call_from_work_profile_title">Can\'t call from this profile</string>
+ <string name="call_from_work_profile_title">Can\'t call from a personal app</string>
<!-- Text for switch to work profile for call dialog to guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=NONE]
-->
- <string name="call_from_work_profile_text">Your work policy allows you to make phone calls only from the work profile</string>
+ <string name="call_from_work_profile_text">Your organization only allows you to make calls from work apps</string>
<!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
<string name="call_from_work_profile_action">Switch to work profile</string>
+ <!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
+ profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+ <string name="install_dialer_on_work_profile_action">Install a work phone app</string>
<!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
- <string name="call_from_work_profile_close">Close</string>
+ <string name="call_from_work_profile_close">Cancel</string>
<!--
Label for a menu item in a menu that is shown when the user wishes to customize the lock screen.
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/CarrierText.java b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
index e4f6e131..87a9b0f 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierText.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierText.java
@@ -33,6 +33,8 @@
private final boolean mShowAirplaneMode;
+ private final String mDebugLocation;
+
public CarrierText(Context context) {
this(context, null);
}
@@ -46,6 +48,7 @@
useAllCaps = a.getBoolean(R.styleable.CarrierText_allCaps, false);
mShowAirplaneMode = a.getBoolean(R.styleable.CarrierText_showAirplaneMode, false);
mShowMissingSim = a.getBoolean(R.styleable.CarrierText_showMissingSim, false);
+ mDebugLocation = a.getString(R.styleable.CarrierText_debugLocation);
} finally {
a.recycle();
}
@@ -70,6 +73,10 @@
return mShowMissingSim;
}
+ public String getDebugLocation() {
+ return mDebugLocation;
+ }
+
private static class CarrierTextTransformationMethod extends SingleLineTransformationMethod {
private final Locale mLocale;
private final boolean mAllCaps;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 997c527..33f9ecd 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -53,6 +53,7 @@
mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(mView.getShowAirplaneMode())
.setShowMissingSim(mView.getShowMissingSim())
+ .setDebugLocationString(mView.getDebugLocation())
.build();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b153785..a724514 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,8 +648,10 @@
private final Executor mMainExecutor;
private final Executor mBgExecutor;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final CarrierTextManagerLogger mLogger;
private boolean mShowAirplaneMode;
private boolean mShowMissingSim;
+ private String mDebugLocation;
@Inject
public Builder(
@@ -658,7 +663,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 +675,7 @@
mMainExecutor = mainExecutor;
mBgExecutor = bgExecutor;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLogger = logger;
}
/** */
@@ -683,14 +690,25 @@
return this;
}
+ /**
+ * To help disambiguate logs, set a location to be used in the LogBuffer calls, e.g.:
+ * "keyguard" or "keyguard emergency status bar"
+ */
+ public Builder setDebugLocationString(String debugLocationString) {
+ mDebugLocation = debugLocationString;
+ return this;
+ }
+
/** Create a CarrierTextManager. */
public CarrierTextManager build() {
+ mLogger.setLocation(mDebugLocation);
return new CarrierTextManager(
mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
- mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+ mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
}
}
+
/**
* Data structure for passing information to CarrierTextController subscribers
*/
@@ -716,6 +734,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/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index ec8fa92..9f21a31 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -19,27 +19,34 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
+import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.os.Bundle;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
+import android.view.DisplayAddress;
import android.view.DisplayInfo;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.concurrent.Executor;
@@ -47,6 +54,7 @@
import dagger.Lazy;
+@SysUISingleton
public class KeyguardDisplayManager {
protected static final String TAG = "KeyguardDisplayManager";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -61,6 +69,9 @@
private boolean mShowing;
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
+ private final DeviceStateHelper mDeviceStateHelper;
+ private final KeyguardStateController mKeyguardStateController;
+
private final SparseArray<Presentation> mPresentations = new SparseArray<>();
private final DisplayTracker.Callback mDisplayCallback =
@@ -92,7 +103,9 @@
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
DisplayTracker displayTracker,
@Main Executor mainExecutor,
- @UiBackground Executor uiBgExecutor) {
+ @UiBackground Executor uiBgExecutor,
+ DeviceStateHelper deviceStateHelper,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
@@ -100,6 +113,8 @@
mDisplayService = mContext.getSystemService(DisplayManager.class);
mDisplayTracker = displayTracker;
mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+ mDeviceStateHelper = deviceStateHelper;
+ mKeyguardStateController = keyguardStateController;
}
private boolean isKeyguardShowable(Display display) {
@@ -122,6 +137,18 @@
}
return false;
}
+ if (mKeyguardStateController.isOccluded()
+ && mDeviceStateHelper.isConcurrentDisplayActive(display)) {
+ if (DEBUG) {
+ // When activities with FLAG_SHOW_WHEN_LOCKED are shown on top of Keyguard, the
+ // Keyguard state becomes "occluded". In this case, we should not show the
+ // KeyguardPresentation, since the activity is presenting content onto the
+ // non-default display.
+ Log.i(TAG, "Do not show KeyguardPresentation when occluded and concurrent"
+ + " display is active");
+ }
+ return false;
+ }
return true;
}
@@ -260,6 +287,53 @@
}
+ /**
+ * Helper used to receive device state info from {@link DeviceStateManager}.
+ */
+ static class DeviceStateHelper implements DeviceStateManager.DeviceStateCallback {
+
+ @Nullable
+ private final DisplayAddress.Physical mRearDisplayPhysicalAddress;
+
+ // TODO(b/271317597): These device states should be defined in DeviceStateManager
+ private final int mConcurrentState;
+ private boolean mIsInConcurrentDisplayState;
+
+ @Inject
+ DeviceStateHelper(Context context,
+ DeviceStateManager deviceStateManager,
+ @Main Executor mainExecutor) {
+
+ final String rearDisplayPhysicalAddress = context.getResources().getString(
+ com.android.internal.R.string.config_rearDisplayPhysicalAddress);
+ if (TextUtils.isEmpty(rearDisplayPhysicalAddress)) {
+ mRearDisplayPhysicalAddress = null;
+ } else {
+ mRearDisplayPhysicalAddress = DisplayAddress
+ .fromPhysicalDisplayId(Long.parseLong(rearDisplayPhysicalAddress));
+ }
+
+ mConcurrentState = context.getResources().getInteger(
+ com.android.internal.R.integer.config_deviceStateConcurrentRearDisplay);
+ deviceStateManager.registerCallback(mainExecutor, this);
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ // When concurrent state ends, the display also turns off. This is enforced in various
+ // ExtensionRearDisplayPresentationTest CTS tests. So, we don't need to invoke
+ // hide() since that will happen through the onDisplayRemoved callback.
+ mIsInConcurrentDisplayState = state == mConcurrentState;
+ }
+
+ boolean isConcurrentDisplayActive(Display display) {
+ return mIsInConcurrentDisplayState
+ && mRearDisplayPhysicalAddress != null
+ && mRearDisplayPhysicalAddress.equals(display.getAddress());
+ }
+ }
+
+
@VisibleForTesting
static final class KeyguardPresentation extends Presentation {
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
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/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5cc0547..4b02171 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -599,7 +599,6 @@
*/
public void startAppearAnimation(SecurityMode securityMode) {
setTranslationY(0f);
- setAlpha(1f);
updateChildren(0 /* translationY */, 1f /* alpha */);
mViewMode.startAppearAnimation(securityMode);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5b1edc7..b5e5420 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -674,7 +674,6 @@
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
- setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController(controller -> controller.startAppearAnimation());
}
@@ -1112,7 +1111,7 @@
*/
public void setExpansion(float fraction) {
float scaledFraction = BouncerPanelExpansionCalculator.showBouncerProgress(fraction);
- mView.setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
+ setAlpha(MathUtils.constrain(1 - scaledFraction, 0f, 1f));
mView.setTranslationY(scaledFraction * mTranslationY);
}
}
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..1978715
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.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] */
+class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) {
+ /**
+ * To help disambiguate carrier text manager instances, set a location string here which will
+ * propagate to [logUpdate] and [logUpdateCarrierTextForReason]
+ */
+ var location: String? = null
+
+ /**
+ * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText
+ */
+ fun logUpdate(numSubs: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { int1 = numSubs },
+ { "updateCarrierText: location=${location ?: "(unknown)"} 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()}" +
+ " location=${location ?: "(unknown)"}"
+ }
+ )
+ }
+
+ 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/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index 632fcdc..2b468cf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -27,6 +27,7 @@
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
@@ -93,6 +94,7 @@
.setBubbles(mWMComponent.getBubbles())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
+ .setKeyguardTransitions(mWMComponent.getKeyguardTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
@@ -113,6 +115,7 @@
.setBubbles(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(new ShellTransitions() {})
+ .setKeyguardTransitions(new KeyguardTransitions() {})
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
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/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index 3349fe5..c3bb423 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -87,19 +87,14 @@
}
/**
- * Shows magnification settings panel {@link WindowMagnificationSettings}. The panel ui would be
- * various for different magnification mode.
- *
- * @param mode The magnification mode
- * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
- * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
+ * Shows magnification settings panel {@link WindowMagnificationSettings}.
*/
- void showMagnificationSettings(int mode) {
+ void showMagnificationSettings() {
if (!mWindowMagnificationSettings.isSettingPanelShowing()) {
onConfigurationChanged(mContext.getResources().getConfiguration());
mContext.registerComponentCallbacks(this);
}
- mWindowMagnificationSettings.showSettingPanel(mode);
+ mWindowMagnificationSettings.showSettingPanel();
}
void closeMagnificationSettings() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index 1c030da..e2b85fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -16,10 +16,10 @@
package com.android.systemui.accessibility;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
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;
@@ -67,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;
@@ -152,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);
@@ -161,6 +162,7 @@
mSysUiState = sysUiState;
mOverviewProxyService = overviewProxyService;
mDisplayTracker = displayTracker;
+ mA11yLogger = a11yLogger;
mMagnificationControllerSupplier = new ControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
displayManager, sysUiState, secureSettings);
@@ -169,8 +171,7 @@
mModeSwitchesController.setClickListenerDelegate(
displayId -> mHandler.post(() -> {
- showMagnificationSettingsPanel(displayId,
- ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ showMagnificationSettingsPanel(displayId);
}));
}
@@ -253,11 +254,11 @@
}
@MainThread
- void showMagnificationSettingsPanel(int displayId, int mode) {
+ void showMagnificationSettingsPanel(int displayId) {
final MagnificationSettingsController magnificationSettingsController =
mMagnificationSettingsSupplier.get(displayId);
if (magnificationSettingsController != null) {
- magnificationSettingsController.showMagnificationSettings(mode);
+ magnificationSettingsController.showMagnificationSettings();
}
}
@@ -334,7 +335,7 @@
@Override
public void onClickSettingsButton(int displayId) {
mHandler.post(() -> {
- showMagnificationSettingsPanel(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ showMagnificationSettingsPanel(displayId);
});
}
};
@@ -345,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
@@ -355,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
@@ -372,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 155c26d..3b1d695 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
@@ -27,6 +28,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.database.ContentObserver;
import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -51,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;
@@ -85,6 +88,7 @@
private SeekBarWithIconButtonsView mZoomSeekbar;
private LinearLayout mAllowDiagonalScrollingView;
+ private TextView mAllowDiagonalScrollingTitle;
private Switch mAllowDiagonalScrollingSwitch;
private LinearLayout mPanelView;
private LinearLayout mSettingView;
@@ -101,8 +105,7 @@
private static final float A11Y_SCALE_MIN_VALUE = 1.0f;
private WindowMagnificationSettingsCallback mCallback;
- // the magnification mode that triggers showing the panel
- private int mTriggeringMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+ private ContentObserver mMagnificationCapabilityObserver;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
@@ -142,6 +145,16 @@
mGestureDetector = new MagnificationGestureDetector(context,
context.getMainThreadHandler(), this);
+
+ mMagnificationCapabilityObserver = new ContentObserver(
+ mContext.getMainThreadHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mSettingView.post(() -> {
+ updateUIControlsIfNeeded();
+ });
+ }
+ };
}
private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
@@ -257,7 +270,7 @@
@Override
public boolean onFinish(float xOffset, float yOffset) {
if (!mSingleTapDetected) {
- showSettingPanel(mTriggeringMode);
+ showSettingPanel();
}
mSingleTapDetected = false;
return true;
@@ -285,7 +298,8 @@
return;
}
- // Reset button status.
+ // Unregister observer before removing view
+ mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver);
mWindowManager.removeView(mSettingView);
mIsVisible = false;
if (resetPosition) {
@@ -297,16 +311,8 @@
mCallback.onSettingsPanelVisibilityChanged(/* shown= */ false);
}
- /**
- * Shows magnification settings panel. The panel ui would be various for
- * different magnification mode.
- *
- * @param mode The magnification mode
- * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
- * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
- */
- public void showSettingPanel(int mode) {
- showSettingPanel(mode, true);
+ public void showSettingPanel() {
+ showSettingPanel(true);
}
public boolean isSettingPanelShowing() {
@@ -322,18 +328,15 @@
}
/**
- * Shows magnification panel for set magnification.
+ * Shows the panel for magnification settings.
* When the panel is going to be visible by calling this method, the layout position can be
* reset depending on the flag.
*
- * @param mode The magnification mode
- * @param resetPosition if the button position needs be reset
+ * @param resetPosition if the panel position needs to be reset
*/
- private void showSettingPanel(int mode, boolean resetPosition) {
+ private void showSettingPanel(boolean resetPosition) {
if (!mIsVisible) {
- updateUIControlsIfNeed(mode);
- mTriggeringMode = mode;
-
+ updateUIControlsIfNeeded();
if (resetPosition) {
mDraggableWindowBounds.set(getDraggableWindowBounds());
mParams.x = mDraggableWindowBounds.right;
@@ -342,6 +345,11 @@
mWindowManager.addView(mSettingView, mParams);
+ mSecureSettings.registerContentObserverForUser(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
+ mMagnificationCapabilityObserver,
+ UserHandle.USER_CURRENT);
+
// Exclude magnification switch button from system gesture area.
setSystemGestureExclusion();
mIsVisible = true;
@@ -359,35 +367,74 @@
mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
- private void updateUIControlsIfNeed(int mode) {
- if (mode == mTriggeringMode) {
- return;
- }
+ private int getMagnificationMode() {
+ return mSecureSettings.getIntForUser(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
+ ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ UserHandle.USER_CURRENT);
+ }
+
+ private int getMagnificationCapability() {
+ return mSecureSettings.getIntForUser(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
+ ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ UserHandle.USER_CURRENT);
+ }
+
+ private void updateUIControlsIfNeeded() {
+ int capability = getMagnificationCapability();
int selectedButtonIndex = mLastSelectedButtonIndex;
- switch (mode) {
+ switch (capability) {
+ case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
+ mEditButton.setVisibility(View.VISIBLE);
+ mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
+ mFullScreenButton.setVisibility(View.GONE);
+ if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
+ selectedButtonIndex = MagnificationSize.NONE;
+ }
+ break;
+
+ case ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+ int mode = getMagnificationMode();
+ mFullScreenButton.setVisibility(View.VISIBLE);
+ if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+ // set the edit button visibility to View.INVISIBLE to keep the height, to
+ // prevent the size title from too close to the size buttons
+ mEditButton.setVisibility(View.INVISIBLE);
+ mAllowDiagonalScrollingView.setVisibility(View.GONE);
+ // force the fullscreen button showing
+ selectedButtonIndex = MagnificationSize.FULLSCREEN;
+ } else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ mEditButton.setVisibility(View.VISIBLE);
+ mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
+ }
+ break;
+
case ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
- // set the edit button visibility to View.INVISIBLE to keep the height, to prevent
- // the size title from too close to the size buttons
+ // We will never fall into this case since we never show settings panel when
+ // capability equals to ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN.
+ // Currently, the case follows the UI controls when capability equals to
+ // ACCESSIBILITY_MAGNIFICATION_MODE_ALL and mode equals to
+ // ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, but we could also consider to
+ // remove the whole icon button selections int the future since they are no use
+ // for fullscreen only capability.
+
+ mFullScreenButton.setVisibility(View.VISIBLE);
+ // set the edit button visibility to View.INVISIBLE to keep the height, to
+ // prevent the size title from too close to the size buttons
mEditButton.setVisibility(View.INVISIBLE);
mAllowDiagonalScrollingView.setVisibility(View.GONE);
// force the fullscreen button showing
selectedButtonIndex = MagnificationSize.FULLSCREEN;
break;
- case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
- mEditButton.setVisibility(View.VISIBLE);
- mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
- if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
- selectedButtonIndex = MagnificationSize.NONE;
- }
- break;
-
default:
break;
}
updateSelectedButton(selectedButtonIndex);
+ mSettingView.requestLayout();
}
private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@@ -422,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(
@@ -445,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
@@ -474,11 +524,11 @@
mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
- boolean showSettingPanelAfterThemeChange = mIsVisible;
+ boolean showSettingPanelAfterConfigChange = mIsVisible;
hideSettingPanel(/* resetPosition= */ false);
inflateView();
- if (showSettingPanelAfterThemeChange) {
- showSettingPanel(mTriggeringMode, /* resetPosition= */ false);
+ if (showSettingPanelAfterConfigChange) {
+ showSettingPanel(/* resetPosition= */ false);
}
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index cd195f6..9d9a87d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -33,7 +33,7 @@
* A device that is not yet unlocked requires unlocking by completing an authentication
* challenge according to the current authentication method.
*
- * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+ * Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
*/
val isUnlocked: StateFlow<Boolean>
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..0782537 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -436,25 +436,36 @@
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)) {
+ 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) {
PorterDuffColorFilter(
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/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 57ce580..8264fed 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -73,7 +73,7 @@
is AuthenticationMethodModel.Swipe ->
sceneInteractor.setCurrentScene(
containerName,
- SceneModel(SceneKey.LockScreen),
+ SceneModel(SceneKey.Lockscreen),
)
else -> Unit
}
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/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 92607c6..d73c85b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -328,6 +328,13 @@
@VisibleForTesting
internal fun startRemovingApp(componentName: ComponentName, appName: CharSequence) {
+ activityStarter.dismissKeyguardThenExecute({
+ showAppRemovalDialog(componentName, appName)
+ true
+ }, null, true)
+ }
+
+ private fun showAppRemovalDialog(componentName: ComponentName, appName: CharSequence) {
removeAppDialog?.cancel()
removeAppDialog = dialogsFactory.createRemoveAppDialog(context, appName) { shouldRemove ->
if (!shouldRemove || !controlsController.get().removeFavorites(componentName)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index dba353b..8d5a2dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -32,6 +32,7 @@
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
+import com.android.systemui.telephony.ui.activity.SwitchToManagedProfileForCallActivity;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbAccessoryUriActivity;
import com.android.systemui.usb.UsbConfirmActivity;
@@ -178,4 +179,11 @@
@ClassKey(TvSensorPrivacyChangedActivity.class)
public abstract Activity bindTvSensorPrivacyChangedActivity(
TvSensorPrivacyChangedActivity activity);
+
+ /** Inject into SwitchToManagedProfileForCallActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(SwitchToManagedProfileForCallActivity.class)
+ public abstract Activity bindSwitchToManagedProfileForCallActivity(
+ SwitchToManagedProfileForCallActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f68bd49..2262d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -40,6 +40,7 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
import com.android.systemui.rotationlock.RotationLockModule;
+import com.android.systemui.scene.SceneContainerFrameworkModule;
import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -103,6 +104,7 @@
QSModule.class,
ReferenceScreenshotModule.class,
RotationLockModule.class,
+ SceneContainerFrameworkModule.class,
StatusBarEventsModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 1a0fcea..52355f3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -42,6 +42,7 @@
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -104,6 +105,9 @@
Builder setTransitions(ShellTransitions t);
@BindsInstance
+ Builder setKeyguardTransitions(KeyguardTransitions k);
+
+ @BindsInstance
Builder setStartingSurface(Optional<StartingSurface> s);
@BindsInstance
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/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 17d2332..b71871e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -31,6 +31,7 @@
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
@@ -99,6 +100,9 @@
ShellTransitions getTransitions();
@WMSingleton
+ KeyguardTransitions getKeyguardTransitions();
+
+ @WMSingleton
Optional<StartingSurface> getStartingSurface();
@WMSingleton
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 26fd086..0b55532 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
@@ -557,7 +562,7 @@
// TODO(b/270987164): Tracking Bug
@JvmField
- val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
+ val TRACKPAD_GESTURE_FEATURES = unreleasedFlag(1205, "trackpad_gesture_features", teamfood = true)
// TODO(b/263826204): Tracking Bug
@JvmField
@@ -608,7 +613,8 @@
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
// TODO(b/278714186) Tracking Bug
- @JvmField val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout")
+ @JvmField val CLIPBOARD_IMAGE_TIMEOUT =
+ unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
// TODO(b/279405451): Tracking Bug
@JvmField
val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag(1703, "clipboard_shared_transitions")
@@ -660,15 +666,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 8b6bd24..54da680 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -21,7 +21,6 @@
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
@@ -37,15 +36,12 @@
import static android.view.WindowManager.TransitionFlags;
import static android.view.WindowManager.TransitionOldType;
import static android.view.WindowManager.TransitionType;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
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;
@@ -67,8 +63,6 @@
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionFilter;
import android.window.TransitionInfo;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -81,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;
@@ -109,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 =
@@ -119,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,35 +156,31 @@
// Wrap Keyguard going away animation.
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
- private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+ public static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
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);
}
@@ -273,7 +247,8 @@
if (mShellTransitions == null || !Transitions.ENABLE_SHELL_TRANSITIONS) {
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter exitAnimationAdapter =
- new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
+ new RemoteAnimationAdapter(
+ mKeyguardViewMediator.getExitAnimationRunner(), 0, 0);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
exitAnimationAdapter);
definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
@@ -297,92 +272,7 @@
unoccludeAnimationAdapter);
ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
mDisplayTracker.getDefaultDisplayId(), definition);
- return;
}
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
- TransitionFilter f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
- mShellTransitions.registerRemote(f, new RemoteTransition(
- wrap(mExitAnimationRunner), getIApplicationThread(), "ExitKeyguard"));
-
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
- // Register for occluding
- final RemoteTransition occludeTransition = new RemoteTransition(
- mOccludeAnimation, getIApplicationThread(), "KeyguardOcclude");
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app showing that occludes.
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- // Then require that we aren't closing any occludes (because this would mean a
- // regular task->task or activity->activity animation not involving keyguard).
- f.mRequirements[1].mNot = true;
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f, occludeTransition);
-
- // Now register for un-occlude.
- final RemoteTransition unoccludeTransition = new RemoteTransition(
- mUnoccludeAnimation, getIApplicationThread(), "KeyguardUnocclude");
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app going-away (doesn't need occlude flag
- // as that is implicit by it having been visible and we don't want to exclude
- // cases where we are un-occluding because the app removed its showWhenLocked
- // capability at runtime).
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- f.mRequirements[1].mMustBeTask = true;
- // Then require that we aren't opening any occludes (otherwise we'd remain
- // occluded).
- f.mRequirements[0].mNot = true;
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- mShellTransitions.registerRemote(f, unoccludeTransition);
-
- // Register for specific transition type.
- // Above filter cannot fulfill all conditions.
- // E.g. close top activity while screen off but next activity is occluded, this should
- // an occluded transition, but since the activity is invisible, the condition would
- // match unoccluded transition.
- // But on the contrary, if we add above condition in occluded transition, then when user
- // trying to dismiss occluded activity when unlock keyguard, the condition would match
- // occluded transition.
- f = new TransitionFilter();
- f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE};
- mShellTransitions.registerRemote(f, occludeTransition);
-
- f = new TransitionFilter();
- f.mTypeSet = new int[]{TRANSIT_KEYGUARD_UNOCCLUDE};
- mShellTransitions.registerRemote(f, unoccludeTransition);
-
- Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_OCCLUDE for DREAM");
- // Register for occluding by Dream
- f = new TransitionFilter();
- f.mFlags = TRANSIT_FLAG_KEYGUARD_LOCKED;
- f.mRequirements = new TransitionFilter.Requirement[]{
- new TransitionFilter.Requirement(), new TransitionFilter.Requirement()};
- // First require at-least one app of type DREAM showing that occludes.
- f.mRequirements[0].mActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM;
- f.mRequirements[0].mMustBeIndependent = false;
- f.mRequirements[0].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
- // Then require that we aren't closing any occludes (because this would mean a
- // regular task->task or activity->activity animation not involving keyguard).
- f.mRequirements[1].mNot = true;
- f.mRequirements[1].mMustBeIndependent = false;
- f.mRequirements[1].mFlags = FLAG_OCCLUDES_KEYGUARD;
- f.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
- mShellTransitions.registerRemote(f, new RemoteTransition(
- wrap(mKeyguardViewMediator.getOccludeByDreamAnimationRunner()),
- getIApplicationThread(), "KeyguardOccludeByDream"));
}
@Override
@@ -402,27 +292,6 @@
}
}
- private final IRemoteAnimationRunner.Stub mExitAnimationRunner =
- new IRemoteAnimationRunner.Stub() {
- @Override // Binder interface
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
- checkPermission();
- mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers,
- nonApps, finishedCallback);
- Trace.endSection();
- }
-
- @Override // Binder interface
- public void onAnimationCancelled() {
- mKeyguardViewMediator.cancelKeyguardExitAnimation();
- }
- };
-
final IRemoteTransition mOccludeAnimation = new IRemoteTransition.Stub() {
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index bd6dfe3..f96f337 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -258,12 +258,10 @@
*/
private var surfaceBehindAlpha = 1f
- private var wallpaperAlpha = 1f
-
@VisibleForTesting
var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
- var wallpaperAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)
+ var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
/**
* Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
@@ -330,6 +328,7 @@
if (surfaceBehindAlpha == 0f) {
Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
surfaceBehindRemoteAnimationTargets = null
+ wallpaperTargets = null
keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
false /* cancelled */)
} else {
@@ -340,23 +339,17 @@
})
}
- with(wallpaperAlphaAnimator) {
+ with(wallpaperCannedUnlockAnimator) {
duration = LAUNCHER_ICONS_ANIMATION_DURATION_MS
interpolator = Interpolators.ALPHA_OUT
addUpdateListener { valueAnimator: ValueAnimator ->
- wallpaperAlpha = valueAnimator.animatedValue as Float
- setWallpaperAppearAmount(wallpaperAlpha)
+ setWallpaperAppearAmount(valueAnimator.animatedValue as Float)
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- Log.d(TAG, "wallpaperAlphaAnimator#onAnimationEnd, animation ended ")
- if (wallpaperAlpha == 1f) {
- wallpaperTargets = null
- keyguardViewMediator.get().finishExitRemoteAnimation()
- } else {
- Log.d(TAG, "wallpaperAlphaAnimator#onAnimationEnd, " +
- "animation was cancelled: skipping finishAnimation()")
- }
+ Log.d(TAG, "wallpaperCannedUnlockAnimator#onAnimationEnd")
+ keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
+ false /* cancelled */)
}
})
}
@@ -387,11 +380,6 @@
context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
}
- fun isAnyKeyguyardAnimatorPlaying(): Boolean {
- return surfaceBehindAlphaAnimator.isStarted ||
- wallpaperAlphaAnimator.isStarted || surfaceBehindEntryAnimator.isStarted
- }
-
/**
* Add a listener to be notified of various stages of the unlock animation.
*/
@@ -536,8 +524,6 @@
wallpaperTargets = wallpapers
surfaceBehindRemoteAnimationStartTime = startTime
- fadeInWallpaper()
-
// If we specifically requested that the surface behind be made visible (vs. it being made
// visible because we're unlocking), then we're in the middle of a swipe-to-unlock touch
// gesture and the surface behind the keyguard should be made visible so that we can animate
@@ -599,7 +585,9 @@
// Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
// Check it here in case there is no more change to the dismiss amount after the last change
// that starts the keyguard animation. @see #updateKeyguardViewMediatorIfThresholdsReached()
- finishKeyguardExitRemoteAnimationIfReachThreshold()
+ if (!playingCannedUnlockAnimation) {
+ finishKeyguardExitRemoteAnimationIfReachThreshold()
+ }
}
/**
@@ -650,7 +638,7 @@
* transition if possible.
*/
private fun unlockToLauncherWithInWindowAnimations() {
- setSurfaceBehindAppearAmount(1f)
+ setSurfaceBehindAppearAmount(1f, wallpapers = false)
try {
// Begin the animation, waiting for the shade to animate out.
@@ -676,19 +664,8 @@
// visible, hide our smartspace.
lockscreenSmartspace?.visibility = View.INVISIBLE
- // As soon as the shade has animated out of the way, finish the keyguard exit animation. The
- // in-window animations in the Launcher window will end on their own.
- handler.postDelayed({
- if (keyguardViewMediator.get().isShowingAndNotOccluded &&
- !keyguardStateController.isKeyguardGoingAway) {
- Log.e(TAG, "Finish keyguard exit animation delayed Runnable ran, but we are " +
- "showing and not going away.")
- return@postDelayed
- }
-
- keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
- false /* cancelled */)
- }, CANNED_UNLOCK_START_DELAY)
+ // Start an animation for the wallpaper, which will finish keyguard exit when it completes.
+ fadeInWallpaper()
}
/**
@@ -809,7 +786,16 @@
* animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
* cancelled).
*/
- fun setSurfaceBehindAppearAmount(amount: Float) {
+ fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
+ val animationAlpha = when {
+ // If we're snapping the keyguard back, immediately begin fading it out.
+ keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
+ // If the screen has turned back off, the unlock animation is going to be cancelled,
+ // so set the surface alpha to 0f so it's no longer visible.
+ !powerManager.isInteractive -> 0f
+ else -> surfaceBehindAlpha
+ }
+
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
@@ -839,16 +825,6 @@
surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
)
-
- val animationAlpha = when {
- // If we're snapping the keyguard back, immediately begin fading it out.
- keyguardStateController.isSnappingKeyguardBackAfterSwipe -> amount
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> surfaceBehindAlpha
- }
-
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
@@ -863,28 +839,27 @@
} else {
applyParamsToSurface(
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+ surfaceBehindRemoteAnimationTarget.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
)
}
}
+
+ if (wallpapers) {
+ setWallpaperAppearAmount(amount)
+ }
}
- /**
- * Modify the opacity of a wallpaper window.
- */
fun setWallpaperAppearAmount(amount: Float) {
- wallpaperTargets?.forEach { wallpaper ->
- val animationAlpha = when {
- // If the screen has turned back off, the unlock animation is going to be cancelled,
- // so set the surface alpha to 0f so it's no longer visible.
- !powerManager.isInteractive -> 0f
- else -> amount
- }
+ val animationAlpha = when {
+ !powerManager.isInteractive -> 0f
+ else -> amount
+ }
+ wallpaperTargets?.forEach { wallpaper ->
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
val sc: SurfaceControl? = wallpaper.leash
@@ -923,6 +898,7 @@
setSurfaceBehindAppearAmount(1f)
surfaceBehindAlphaAnimator.cancel()
surfaceBehindEntryAnimator.cancel()
+ wallpaperCannedUnlockAnimator.cancel()
try {
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
} catch (e: RemoteException) {
@@ -931,6 +907,7 @@
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
+ wallpaperTargets = null
playingCannedUnlockAnimation = false
willUnlockWithInWindowLauncherAnimations = false
@@ -972,8 +949,8 @@
private fun fadeInWallpaper() {
Log.d(TAG, "fadeInWallpaper")
- wallpaperAlphaAnimator.cancel()
- wallpaperAlphaAnimator.start()
+ wallpaperCannedUnlockAnimator.cancel()
+ wallpaperCannedUnlockAnimator.start()
}
private fun fadeOutSurfaceBehind() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ddc12c9..45e4623 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -93,6 +93,7 @@
import android.view.WindowManagerPolicyConstants;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.window.IRemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -153,12 +154,14 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Optional;
import java.util.concurrent.Executor;
/**
@@ -961,7 +964,26 @@
}
};
- private IRemoteAnimationRunner mOccludeAnimationRunner =
+ private final IRemoteAnimationRunner.Stub mExitAnimationRunner =
+ new IRemoteAnimationRunner.Stub() {
+ @Override // Binder interface
+ public void onAnimationStart(@WindowManager.TransitionOldType int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
+ startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+ Trace.endSection();
+ }
+
+ @Override // Binder interface
+ public void onAnimationCancelled() {
+ cancelKeyguardExitAnimation();
+ }
+ };
+
+ private final IRemoteAnimationRunner mOccludeAnimationRunner =
new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController);
private final IRemoteAnimationRunner mOccludeByDreamAnimationRunner =
@@ -1003,7 +1025,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo.topActivityType
+ final boolean isDream = (apps[0].taskInfo != null
+ && apps[0].taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
if (!isDream) {
Log.w(TAG, "The occluding app isn't Dream; "
@@ -1103,7 +1126,8 @@
}
final RemoteAnimationTarget primary = apps[0];
- final boolean isDream = (apps[0].taskInfo.topActivityType
+ final boolean isDream = (apps[0].taskInfo != null
+ && apps[0].taskInfo.topActivityType
== WindowConfiguration.ACTIVITY_TYPE_DREAM);
final SyncRtSurfaceTransactionApplier applier =
@@ -1186,6 +1210,7 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private boolean mWallpaperSupportsAmbientMode;
private ScreenOnCoordinator mScreenOnCoordinator;
+ private final KeyguardTransitions mKeyguardTransitions;
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
private Lazy<ScrimController> mScrimControllerLazy;
@@ -1221,6 +1246,7 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
+ KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeControllerLazy,
@@ -1248,6 +1274,7 @@
dumpManager.registerDumpable(getClass().getName(), this);
mDeviceConfig = deviceConfig;
mScreenOnCoordinator = screenOnCoordinator;
+ mKeyguardTransitions = keyguardTransitions;
mNotificationShadeWindowControllerLazy = notificationShadeWindowControllerLazy;
mShowHomeOverLockscreen = mDeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -1323,6 +1350,12 @@
setShowingLocked(false /* showing */, true /* forceCallbacks */);
}
+ mKeyguardTransitions.register(
+ KeyguardService.wrap(getExitAnimationRunner()),
+ KeyguardService.wrap(getOccludeAnimationRunner()),
+ KeyguardService.wrap(getOccludeByDreamAnimationRunner()),
+ KeyguardService.wrap(getUnoccludeAnimationRunner()));
+
final ContentResolver cr = mContext.getContentResolver();
mDeviceInteractive = mPM.isInteractive();
@@ -1468,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) {
@@ -1673,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(
@@ -1858,6 +1901,10 @@
Trace.endSection();
}
+ public IRemoteAnimationRunner getExitAnimationRunner() {
+ return mExitAnimationRunner;
+ }
+
public IRemoteAnimationRunner getOccludeAnimationRunner() {
return mOccludeAnimationRunner;
}
@@ -1896,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();
}
@@ -2955,19 +3009,6 @@
mSurfaceBehindRemoteAnimationRequested = false;
mSurfaceBehindRemoteAnimationRunning = false;
mKeyguardStateController.notifyKeyguardGoingAway(false);
- finishExitRemoteAnimation();
- }
-
- void finishExitRemoteAnimation() {
- if (mKeyguardUnlockAnimationControllerLazy.get().isAnyKeyguyardAnimatorPlaying()
- || mKeyguardStateController.isDismissingFromSwipe()) {
- // If the animation is ongoing, or we are not done with the swipe gesture,
- // it's too early to terminate the animation
- Log.d(TAG, "finishAnimation not executing now because "
- + "not all animations have finished");
- return;
- }
- Log.d(TAG, "finishAnimation executing");
if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
try {
@@ -3019,6 +3060,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 deb8f5d..d7c039d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -64,6 +64,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import dagger.Lazy;
import dagger.Module;
@@ -86,6 +87,7 @@
KeyguardRepositoryModule.class,
KeyguardFaceAuthModule.class,
StartKeyguardTransitionModule.class,
+ ResourceTrimmerModule.class,
})
public class KeyguardModule {
/**
@@ -119,6 +121,7 @@
ScreenOffAnimationController screenOffAnimationController,
Lazy<NotificationShadeDepthController> notificationShadeDepthController,
ScreenOnCoordinator screenOnCoordinator,
+ KeyguardTransitions keyguardTransitions,
InteractionJankMonitor interactionJankMonitor,
DreamOverlayStateController dreamOverlayStateController,
Lazy<ShadeController> shadeController,
@@ -152,6 +155,7 @@
screenOffAnimationController,
notificationShadeDepthController,
screenOnCoordinator,
+ keyguardTransitions,
interactionJankMonitor,
dreamOverlayStateController,
shadeController,
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/LockScreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
index 6170180..d0bc25f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
@@ -36,8 +36,8 @@
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-/** Hosts business and application state accessing logic for the lock screen scene. */
-class LockScreenSceneInteractor
+/** Hosts business and application state accessing logic for the lockscreen scene. */
+class LockscreenSceneInteractor
@AssistedInject
constructor(
@Application applicationScope: CoroutineScope,
@@ -59,7 +59,7 @@
initialValue = !authenticationInteractor.isUnlocked.value,
)
- /** Whether it's currently possible to swipe up to dismiss the lock screen. */
+ /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
val isSwipeToDismissEnabled: StateFlow<Boolean> =
combine(
authenticationInteractor.isUnlocked,
@@ -81,9 +81,9 @@
)
init {
- // LOCKING SHOWS LOCK SCREEN.
+ // LOCKING SHOWS Lockscreen.
//
- // Move to the lock screen scene if the device becomes locked while in any scene.
+ // Move to the lockscreen scene if the device becomes locked while in any scene.
applicationScope.launch {
authenticationInteractor.isUnlocked
.map { !it }
@@ -92,7 +92,7 @@
if (isLocked) {
sceneInteractor.setCurrentScene(
containerName = containerName,
- scene = SceneModel(SceneKey.LockScreen),
+ scene = SceneModel(SceneKey.Lockscreen),
)
}
}
@@ -101,7 +101,7 @@
// BYPASS UNLOCK.
//
// Moves to the gone scene if bypass is enabled and the device becomes unlocked while in the
- // lock screen scene.
+ // lockscreen scene.
applicationScope.launch {
combine(
authenticationInteractor.isBypassEnabled,
@@ -110,7 +110,7 @@
::Triple,
)
.collect { (isBypassEnabled, isUnlocked, currentScene) ->
- if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.LockScreen) {
+ if (isBypassEnabled && isUnlocked && currentScene.key == SceneKey.Lockscreen) {
sceneInteractor.setCurrentScene(
containerName = containerName,
scene = SceneModel(SceneKey.Gone),
@@ -119,9 +119,9 @@
}
}
- // SWIPE TO DISMISS LOCK SCREEN.
+ // SWIPE TO DISMISS Lockscreen.
//
- // If switched from the lock screen to the gone scene and the auth method was a swipe,
+ // If switched from the lockscreen to the gone scene and the auth method was a swipe,
// unlocks the device.
applicationScope.launch {
combine(
@@ -133,7 +133,7 @@
val (previousScene, currentScene) = scenes
if (
authMethod is AuthenticationMethodModel.Swipe &&
- previousScene.key == SceneKey.LockScreen &&
+ previousScene.key == SceneKey.Lockscreen &&
currentScene.key == SceneKey.Gone
) {
authenticationInteractor.unlockDevice()
@@ -141,9 +141,9 @@
}
}
- // DISMISS LOCK SCREEN IF AUTH METHOD IS REMOVED.
+ // DISMISS Lockscreen IF AUTH METHOD IS REMOVED.
//
- // If the auth method becomes None while on the lock screen scene, dismisses the lock
+ // If the auth method becomes None while on the lockscreen scene, dismisses the lock
// screen.
applicationScope.launch {
combine(
@@ -153,7 +153,7 @@
)
.collect { (authMethod, scene) ->
if (
- scene.key == SceneKey.LockScreen &&
+ scene.key == SceneKey.Lockscreen &&
authMethod == AuthenticationMethodModel.None
) {
sceneInteractor.setCurrentScene(
@@ -165,8 +165,8 @@
}
}
- /** Attempts to dismiss the lock screen. This will cause the bouncer to show, if needed. */
- fun dismissLockScreen() {
+ /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
+ fun dismissLockscreen() {
bouncerInteractor.showOrUnlockDevice(containerName = containerName)
}
@@ -181,6 +181,6 @@
interface Factory {
fun create(
containerName: String,
- ): LockScreenSceneInteractor
+ ): LockscreenSceneInteractor
}
}
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..54bc349 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
@@ -39,15 +40,19 @@
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
+import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
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 +63,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 +110,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 +129,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.
@@ -183,6 +197,7 @@
cancelShowRunnable()
repository.setPrimaryShowingSoon(false)
repository.setPrimaryShow(false)
+ repository.setPanelExpansion(EXPANSION_HIDDEN)
primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
Trace.endSection()
}
@@ -377,8 +392,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/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index a72490b..555a09b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -25,6 +25,7 @@
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.Bundle
+import android.os.Handler
import android.os.IBinder
import android.view.LayoutInflater
import android.view.SurfaceControlViewHost
@@ -58,6 +59,7 @@
constructor(
@Application private val context: Context,
@Main private val mainDispatcher: CoroutineDispatcher,
+ @Main private val mainHandler: Handler,
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
@@ -113,7 +115,7 @@
}
fun render() {
- runBlocking(mainDispatcher) {
+ mainHandler.post {
val rootView = FrameLayout(context)
setUpBottomArea(rootView)
@@ -122,7 +124,9 @@
setUpUdfps(rootView)
- setUpClock(rootView)
+ if (!shouldHideClock) {
+ setUpClock(rootView)
+ }
rootView.measure(
View.MeasureSpec.makeMeasureSpec(
@@ -169,14 +173,12 @@
* @param hide TRUE hides smartspace, FALSE shows smartspace
*/
fun hideSmartspace(hide: Boolean) {
- runBlocking(mainDispatcher) {
- smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE
- }
+ mainHandler.post { smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE }
}
/** Sets the clock's color to the overridden seed color. */
fun onColorOverridden(@ColorInt color: Int?) {
- runBlocking(mainDispatcher) {
+ mainHandler.post {
colorOverride = color
clockController.clock?.run { events.onSeedColorChanged(color) }
}
@@ -352,28 +354,23 @@
clockController.clock = clock
colorOverride?.let { clock.events.onSeedColorChanged(it) }
- if (!shouldHideClock) {
- clock.largeClock.events.onTargetRegionChanged(
- KeyguardClockSwitch.getLargeClockRegion(parentView)
- )
- clockView?.let { parentView.removeView(it) }
- clockView =
- clock.largeClock.view.apply {
- if (shouldHighlightSelectedAffordance) {
- alpha = DIM_ALPHA
- }
- parentView.addView(this)
- visibility = View.VISIBLE
+ clock.largeClock.events.onTargetRegionChanged(
+ KeyguardClockSwitch.getLargeClockRegion(parentView)
+ )
+
+ clockView?.let { parentView.removeView(it) }
+ clockView =
+ clock.largeClock.view.apply {
+ if (shouldHighlightSelectedAffordance) {
+ alpha = DIM_ALPHA
}
- } else {
- clockView?.visibility = View.GONE
- }
+ parentView.addView(this)
+ visibility = View.VISIBLE
+ }
// Hide smart space if the clock has weather display; otherwise show it
- val hasCustomWeatherDataDisplay =
- clock.largeClock.config.hasCustomWeatherDataDisplay == true
- hideSmartspace(hasCustomWeatherDataDisplay)
+ hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
similarity index 88%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 08b9613..f212a55 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -20,7 +20,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -31,17 +31,17 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-/** Models UI state and handles user input for the lock screen scene. */
-class LockScreenSceneViewModel
+/** Models UI state and handles user input for the lockscreen scene. */
+class LockscreenSceneViewModel
@AssistedInject
constructor(
@Application applicationScope: CoroutineScope,
- interactorFactory: LockScreenSceneInteractor.Factory,
+ interactorFactory: LockscreenSceneInteractor.Factory,
@Assisted containerName: String,
) {
- private val interactor: LockScreenSceneInteractor = interactorFactory.create(containerName)
+ private val interactor: LockscreenSceneInteractor = interactorFactory.create(containerName)
- /** The icon for the "lock" button on the lock screen. */
+ /** The icon for the "lock" button on the lockscreen. */
val lockButtonIcon: StateFlow<Icon> =
interactor.isDeviceLocked
.map { isLocked -> lockIcon(isLocked = isLocked) }
@@ -63,12 +63,12 @@
/** Notifies that the lock button on the lock screen was clicked. */
fun onLockButtonClicked() {
- interactor.dismissLockScreen()
+ interactor.dismissLockscreen()
}
/** Notifies that some content on the lock screen was clicked. */
fun onContentClicked() {
- interactor.dismissLockScreen()
+ interactor.dismissLockscreen()
}
private fun upDestinationSceneKey(
@@ -103,6 +103,6 @@
interface Factory {
fun create(
containerName: String,
- ): LockScreenSceneViewModel
+ ): LockscreenSceneViewModel
}
}
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 b6fcd82..0261ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -60,7 +60,7 @@
if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
maxSize *= 10;
}
- return factory.create("NotifLog", maxSize, false /* systrace */);
+ return factory.create("NotifLog", maxSize, Compile.IS_DEBUG /* systrace */);
}
/** Provides a logging buffer for all logs related to notifications on the lockscreen. */
@@ -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 d949cf56f..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());
- }
- initMutingExpectedDevice();
+ 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 151dbb2..af06258 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -47,7 +47,6 @@
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.settingslib.Utils;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
@@ -90,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;
}
@@ -174,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();
}
@@ -198,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()));
}
@@ -231,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);
@@ -247,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()));
}
@@ -273,34 +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();
- backgroundDrawable.setTint(
- 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();
- backgroundDrawable.setTint(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);
@@ -318,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) {
@@ -339,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();
}
@@ -377,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();
}
}
@@ -395,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;
}
@@ -443,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) {
@@ -455,11 +416,16 @@
void initMutingExpectedDevice() {
disableSeekBar();
+ updateTitleIcon(R.drawable.media_output_icon_volume,
+ mController.getColorItemContent());
final Drawable backgroundDrawable = mContext.getDrawable(
R.drawable.media_output_item_background_active)
.mutate();
- backgroundDrawable.setTint(mController.getColorConnectedItemBackground());
mItemLayout.setBackground(backgroundDrawable);
+ mItemLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorConnectedItemBackground()));
+ mIconAreaLayout.setBackgroundTintList(
+ ColorStateList.valueOf(mController.getColorConnectedItemBackground()));
}
private void animateCornerAndVolume(int fromProgress, int toProgress) {
@@ -469,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);
@@ -530,20 +490,10 @@
});
}
- Drawable getSpeakerDrawable() {
- final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
- .mutate();
- drawable.setTint(Utils.getColorStateListDefaultColor(mContext,
- R.color.media_dialog_item_main_content));
- return drawable;
- }
-
protected void disableSeekBar() {
mSeekBar.setEnabled(false);
mSeekBar.setOnTouchListener((v, event) -> true);
- if (mController.isAdvancedLayoutSupported()) {
- updateIconAreaClickListener(null);
- }
+ updateIconAreaClickListener(null);
}
private void enableSeekBar(MediaDevice device) {
@@ -574,7 +524,6 @@
return;
}
mTitleIcon.setImageIcon(icon);
- icon.setTint(mController.getColorItemContent());
mTitleIcon.setImageTintList(
ColorStateList.valueOf(mController.getColorItemContent()));
});
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/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 46724ad..d889979 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -24,6 +24,7 @@
import android.widget.LinearLayout;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
@@ -198,13 +199,23 @@
@Override
public boolean updateResources() {
- mCellHeightResId = R.dimen.qs_quick_tile_size;
+ mResourceCellHeightResId = R.dimen.qs_quick_tile_size;
boolean b = super.updateResources();
mMaxAllowedRows = getResources().getInteger(R.integer.quick_qs_panel_max_rows);
return b;
}
@Override
+ protected void estimateCellHeight() {
+ FontSizeUtils.updateFontSize(mTempTextView, R.dimen.qs_tile_text_size);
+ int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mTempTextView.measure(unspecifiedSpec, unspecifiedSpec);
+ int padding = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
+ // the QQS only have 1 label
+ mEstimatedCellHeight = mTempTextView.getMeasuredHeight() + padding * 2;
+ }
+
+ @Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 269a158..19bf018 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -9,10 +9,12 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
@@ -29,9 +31,10 @@
protected int mColumns;
protected int mCellWidth;
- protected int mCellHeightResId = R.dimen.qs_tile_height;
+ protected int mResourceCellHeightResId = R.dimen.qs_tile_height;
+ protected int mResourceCellHeight;
+ protected int mEstimatedCellHeight;
protected int mCellHeight;
- protected int mMaxCellHeight;
protected int mCellMarginHorizontal;
protected int mCellMarginVertical;
protected int mSidePadding;
@@ -49,6 +52,8 @@
private float mSquishinessFraction = 1f;
protected int mLastTileBottom;
+ protected TextView mTempTextView;
+
public TileLayout(Context context) {
this(context, null);
}
@@ -57,6 +62,7 @@
super(context, attrs);
mLessRows = ((Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0)
|| useQsMediaPlayer(context));
+ mTempTextView = new TextView(context);
updateResources();
}
@@ -120,14 +126,19 @@
}
public boolean updateResources() {
- final Resources res = mContext.getResources();
+ Resources res = getResources();
mResourceColumns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
- mMaxCellHeight = mContext.getResources().getDimensionPixelSize(mCellHeightResId);
+ mResourceCellHeight = res.getDimensionPixelSize(mResourceCellHeightResId);
mCellMarginHorizontal = res.getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal);
mSidePadding = useSidePadding() ? mCellMarginHorizontal / 2 : 0;
mCellMarginVertical= res.getDimensionPixelSize(R.dimen.qs_tile_margin_vertical);
mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
- if (mLessRows) mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
+ if (mLessRows) {
+ mMaxAllowedRows = Math.max(mMinRows, mMaxAllowedRows - 1);
+ }
+ // update estimated cell height under current font scaling
+ mTempTextView.dispatchConfigurationChanged(mContext.getResources().getConfiguration());
+ estimateCellHeight();
if (updateColumns()) {
requestLayout();
return true;
@@ -211,8 +222,23 @@
return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
}
+ // Estimate the height for the tile with 2 labels (general case) under current font scaling.
+ protected void estimateCellHeight() {
+ FontSizeUtils.updateFontSize(mTempTextView, R.dimen.qs_tile_text_size);
+ int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mTempTextView.measure(unspecifiedSpec, unspecifiedSpec);
+ int padding = mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
+ mEstimatedCellHeight = mTempTextView.getMeasuredHeight() * 2 + padding * 2;
+ }
+
protected int getCellHeight() {
- return mMaxCellHeight;
+ // Compare estimated height with resource height and return the larger one.
+ // If estimated height > resource height, it means the resource height is not enough
+ // for the tile content under current font scaling. Therefore, we need to use the estimated
+ // height to have a full tile content view.
+ // If estimated height <= resource height, we can use the resource height for tile to keep
+ // the same UI as original behavior.
+ return Math.max(mResourceCellHeight, mEstimatedCellHeight);
}
private void layoutTileRecords(int numRecords, boolean forLayout) {
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/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 6525a98..36dec1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,7 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -25,15 +25,15 @@
class QuickSettingsSceneViewModel
@AssistedInject
constructor(
- lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+ lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
@Assisted containerName: String,
) {
- private val lockScreenSceneInteractor: LockScreenSceneInteractor =
- lockScreenSceneInteractorFactory.create(containerName)
+ private val lockscreenSceneInteractor: LockscreenSceneInteractor =
+ lockscreenSceneInteractorFactory.create(containerName)
/** Notifies that some content in quick settings was clicked. */
fun onContentClicked() {
- lockScreenSceneInteractor.dismissLockScreen()
+ lockscreenSceneInteractor.dismissLockscreen()
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
new file mode 100644
index 0000000..0ed8b21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.scene.shared.page.SceneModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ SceneModule::class,
+ ],
+)
+object SceneContainerFrameworkModule
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
index 9ef439d..e7811e3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt
@@ -32,8 +32,8 @@
*/
object Gone : SceneKey("gone")
- /** The lock screen is the scene that shows when the device is locked. */
- object LockScreen : SceneKey("lockscreen")
+ /** The lockscreen is the scene that shows when the device is locked. */
+ object Lockscreen : SceneKey("lockscreen")
/**
* The shade is the scene whose primary purpose is to show a scrollable list of notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index 8c01bae..5ad16f0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -293,10 +293,9 @@
final ContentValues values = createMetadata(time, format, fileName);
Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- if (UserHandle.myUserId() != owner.getIdentifier()) {
- baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
- }
- Uri uri = resolver.insert(baseUri, values);
+ Uri uriWithUserId = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
+
+ Uri uri = resolver.insert(uriWithUserId, values);
if (uri == null) {
throw new ImageExportException(RESOLVER_INSERT_RETURNED_NULL);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 7261324..2f96f6c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -247,7 +247,7 @@
}
updateImageDimensions();
- mViewModel.saveScreenshotThenFinish(drawable, bounds);
+ mViewModel.saveScreenshotThenFinish(drawable, bounds, getUser());
}
private void setResultThenFinish(Uri uri) {
@@ -255,6 +255,11 @@
return;
}
+ // Grant permission here instead of in the trampoline activity because this activity can run
+ // as work profile user so the URI can belong to the work profile user while the trampoline
+ // activity always runs as main user.
+ grantUriPermission(mCallingPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
Bundle data = new Bundle();
data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index e1619dc..afc8bff 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.view.Display;
+import android.os.UserManager;
import androidx.annotation.Nullable;
@@ -27,6 +27,7 @@
import com.android.internal.infra.ServiceConnector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.settings.DisplayTracker;
import javax.inject.Inject;
@@ -35,14 +36,20 @@
class AppClipsCrossProcessHelper {
private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
+ private final DisplayTracker mDisplayTracker;
@Inject
- AppClipsCrossProcessHelper(@Application Context context) {
- mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context,
+ AppClipsCrossProcessHelper(@Application Context context, UserManager userManager,
+ DisplayTracker displayTracker) {
+ // Start a service as main user so that even if the app clips activity is running as work
+ // profile user the service is able to use correct instance of Bubbles to grab a screenshot
+ // excluding the bubble layer.
+ mProxyConnector = new ServiceConnector.Impl<>(context,
new Intent(context, AppClipsScreenshotHelperService.class),
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
- | Context.BIND_NOT_VISIBLE, context.getUserId(),
+ | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(),
IAppClipsScreenshotHelperService.Stub::asInterface);
+ mDisplayTracker = displayTracker;
}
/**
@@ -56,7 +63,9 @@
try {
AndroidFuture<ScreenshotHardwareBufferInternal> future =
mProxyConnector.postForResult(
- service -> service.takeScreenshot(Display.DEFAULT_DISPLAY));
+ service ->
+ // Take a screenshot of the default display of the user.
+ service.takeScreenshot(mDisplayTracker.getDefaultDisplayId()));
return future.get().createBitmapThenCloseBuffer();
} catch (Exception e) {
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 0487cbc..f00803c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -21,7 +21,6 @@
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
-import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
@@ -34,6 +33,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
@@ -82,6 +82,8 @@
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+ @VisibleForTesting
+ static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER";
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
@@ -98,6 +100,7 @@
private final ResultReceiver mResultReceiver;
private Intent mKillAppClipsBroadcastIntent;
+ private UserHandle mNotesAppUser;
@Inject
public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
@@ -165,15 +168,21 @@
return;
}
+ mNotesAppUser = getUser();
+ if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) {
+ // Get the work profile user internally instead of passing around via intent extras as
+ // this activity is exported apps could potentially mess around with intent extras.
+ mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser);
+ }
+
String callingPackageName = getCallingPackage();
Intent intent = new Intent().setComponent(componentName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
.putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
-
try {
- // Start the App Clips activity.
- startActivity(intent);
+ // Start the App Clips activity for the user corresponding to the notes app user.
+ startActivityAsUser(intent, mNotesAppUser);
// Set up the broadcast intent that will inform the above App Clips activity to finish
// when this trampoline activity is finished.
@@ -198,6 +207,13 @@
}
}
+ private Optional<UserHandle> getWorkProfileUser() {
+ return mUserTracker.getUserProfiles().stream()
+ .filter(profile -> mUserManager.isManagedProfile(profile.id))
+ .findFirst()
+ .map(UserInfo::getUserHandle);
+ }
+
private void maybeStartActivityForWPUser() {
UserHandle mainUser = mUserManager.getMainUser();
if (mainUser == null) {
@@ -205,9 +221,13 @@
return;
}
- // Start the activity as the main user with activity result forwarding.
+ // Start the activity as the main user with activity result forwarding. Set the intent extra
+ // so that the newly started trampoline activity starts the actual app clips activity as the
+ // work profile user. Starting the app clips activity as the work profile user is required
+ // to save the screenshot in work profile user storage and grant read permission to the URI.
startActivityAsUser(
new Intent(this, AppClipsTrampolineActivity.class)
+ .putExtra(EXTRA_USE_WP_USER, /* value= */ true)
.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
}
@@ -221,7 +241,7 @@
int callingPackageUid = 0;
try {
callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
- APPLICATION_INFO_FLAGS, mUserTracker.getUserId()).uid;
+ APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid;
} catch (NameNotFoundException e) {
Log.d(TAG, "Couldn't find notes app UID " + e);
}
@@ -254,14 +274,14 @@
if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class);
- convertedData.setData(uri).addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+ convertedData.setData(uri);
}
// Broadcast no longer required, setting it to null.
mKillAppClipsBroadcastIntent = null;
// Expand the note bubble before returning the result.
- mNoteTaskController.showNoteTask(NoteTaskEntryPoint.APP_CLIPS);
+ mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser);
setResult(RESULT_OK, convertedData);
finish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 4cbca28a..b0e4cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -26,7 +26,7 @@
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Process;
+import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
@@ -110,16 +110,14 @@
* Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
* {@link LiveData}.
*/
- void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
+ void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds, UserHandle user) {
mBgExecutor.execute(() -> {
// Render the screenshot bitmap in background.
Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
// Export and save the screenshot in background.
- // TODO(b/267310185): Save to work profile UserHandle.
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
- mBgExecutor, UUID.randomUUID(), screenshotBitmap,
- Process.myUserHandle());
+ mBgExecutor, UUID.randomUUID(), screenshotBitmap, user);
// Get the result and update state on main thread.
exportFuture.addListener(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
index 2336673..9235fcc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java
@@ -84,7 +84,7 @@
Color.YELLOW, "calculatePanelHeightShade()");
drawDebugInfo(canvas,
(int) mQsController.calculateNotificationsTopPadding(
- mNotificationPanelViewController.isExpanding(),
+ mNotificationPanelViewController.isExpandingOrCollapsing(),
mNotificationPanelViewController.getKeyguardNotificationStaticPadding(),
mNotificationPanelViewController.getExpandedFraction()),
Color.MAGENTA, "calculateNotificationsTopPadding()");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index af12bc2..0fdd7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -233,7 +233,6 @@
import javax.inject.Provider;
import kotlin.Unit;
-
import kotlinx.coroutines.CoroutineDispatcher;
@CentralSurfacesComponent.CentralSurfacesScope
@@ -415,7 +414,11 @@
private final KeyguardClockPositionAlgorithm.Result
mClockPositionResult =
new KeyguardClockPositionAlgorithm.Result();
- private boolean mIsExpanding;
+ /**
+ * Indicates shade (or just QS) is expanding or collapsing but doesn't fully cover KEYGUARD
+ * state when shade can be expanded with swipe down or swipe down from the top to full QS.
+ */
+ private boolean mIsExpandingOrCollapsing;
/**
* Indicates drag starting height when swiping down or up on heads-up notifications.
@@ -1862,7 +1865,7 @@
@Override
public void expandToNotifications() {
- if (mSplitShadeEnabled && (isShadeFullyExpanded() || isExpanding())) {
+ if (mSplitShadeEnabled && (isShadeFullyExpanded() || isExpandingOrCollapsing())) {
return;
}
if (mQsController.getExpanded()) {
@@ -2109,6 +2112,9 @@
? QUICK_SETTINGS : (
mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK);
if (!isFalseTouch(x, y, interactionType)) {
+ mShadeLog.logFlingExpands(vel, vectorVel, interactionType,
+ this.mFlingAnimationUtils.getMinVelocityPxPerSecond(),
+ mExpandedFraction > 0.5f, mAllowExpandForSmallExpansion);
if (Math.abs(vectorVel) < this.mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
expands = shouldExpandWhenNotFlinging();
} else {
@@ -2269,7 +2275,7 @@
void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScrollLayoutController.updateTopPadding(
- mQsController.calculateNotificationsTopPadding(mIsExpanding,
+ mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
getKeyguardNotificationStaticPadding(), mExpandedFraction), animate);
if (isKeyguardShowing()
&& mKeyguardBypassController.getBypassEnabled()) {
@@ -2322,7 +2328,7 @@
}
int maxHeight;
if (mQsController.isExpandImmediate() || mQsController.getExpanded()
- || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()
+ || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()
|| mPulsing || mSplitShadeEnabled) {
maxHeight = mQsController.calculatePanelHeightExpanded(
mClockPositionResult.stackScrollerPadding);
@@ -2342,8 +2348,11 @@
return maxHeight;
}
- public boolean isExpanding() {
- return mIsExpanding;
+ @Override
+ public boolean isExpandingOrCollapsing() {
+ float lockscreenExpansionProgress = mQsController.getLockscreenShadeDragProgress();
+ return mIsExpandingOrCollapsing
+ || (0 < lockscreenExpansionProgress && lockscreenExpansionProgress < 1);
}
private void onHeightUpdated(float expandedHeight) {
@@ -2355,7 +2364,7 @@
mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
}
if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
- || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()) {
+ || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
// Updating the clock position will set the top padding which might
// trigger a new panel height and re-position the clock.
// This is a circular dependency and should be avoided, otherwise we'll have
@@ -2493,7 +2502,7 @@
mNotificationStackScrollLayoutController.onExpansionStopped();
mHeadsUpManager.onExpandingFinished();
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
- mIsExpanding = false;
+ mIsExpandingOrCollapsing = false;
mMediaHierarchyManager.setCollapsingShadeFromQS(false);
mMediaHierarchyManager.setQsExpanded(mQsController.getExpanded());
if (isFullyCollapsed()) {
@@ -2908,6 +2917,10 @@
&& mBarState == StatusBarState.SHADE;
}
+ private boolean isPanelVisibleBecauseScrimIsAnimatingOff() {
+ return mUnlockedScreenOffAnimationController.isAnimationPlaying();
+ }
+
@Override
public boolean shouldHideStatusBarIconsWhenExpanded() {
if (mIsLaunchAnimationRunning) {
@@ -3199,7 +3212,7 @@
ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
- ipw.print("mIsExpanding="); ipw.println(mIsExpanding);
+ ipw.print("mIsExpandingOrCollapsing="); ipw.println(mIsExpandingOrCollapsing);
ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight);
ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp);
ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight);
@@ -3431,7 +3444,7 @@
void notifyExpandingStarted() {
if (!mExpanding) {
mExpanding = true;
- mIsExpanding = true;
+ mIsExpandingOrCollapsing = true;
mQsController.onExpandingStarted(mQsController.getFullyExpanded());
}
}
@@ -3492,7 +3505,7 @@
* gesture), we always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
- if (mVibrateOnOpening) {
+ if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
if (!openingWithTouch || !mHasVibratedOnOpen) {
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
mHasVibratedOnOpen = true;
@@ -3792,7 +3805,7 @@
} else if (mBarState == SHADE_LOCKED) {
return true;
} else {
- // case of two finger swipe from the top of keyguard
+ // case of swipe from the top of keyguard to expanded QS
return mQsController.computeExpansionFraction() == 1;
}
}
@@ -3967,6 +3980,7 @@
|| isPanelVisibleBecauseOfHeadsUp()
|| mTracking
|| mHeightAnimator != null
+ || isPanelVisibleBecauseScrimIsAnimatingOff()
&& !mIsSpringBackAnimation;
}
@@ -4044,7 +4058,7 @@
* shade QS are always expanded
*/
private void closeQsIfPossible() {
- boolean openOrOpening = isShadeFullyExpanded() || isExpanding();
+ boolean openOrOpening = isShadeFullyExpanded() || isExpandingOrCollapsing();
if (!(mSplitShadeEnabled && openOrOpening)) {
mQsController.closeQs();
}
@@ -4767,7 +4781,7 @@
// If pulse is expanding already, let's give it the touch. There are situations
// where the panel starts expanding even though we're also pulsing
- boolean pulseShouldGetTouch = (!mIsExpanding
+ boolean pulseShouldGetTouch = (!mIsExpandingOrCollapsing
&& !mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0))
|| mPulseExpansionHandler.isExpanding();
if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
@@ -4933,7 +4947,7 @@
mShadeLog.logHasVibrated(mHasVibratedOnOpen, mExpandedFraction);
}
addMovement(event);
- if (!isFullyCollapsed() && !isOnKeyguard()) {
+ if (!isFullyCollapsed()) {
maybeVibrateOnOpening(true /* openingWithTouch */);
}
float h = y - mInitialExpandY;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index e08bc33..d0a3cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -78,6 +78,11 @@
boolean isShadeFullyOpen();
/**
+ * Returns whether shade or QS are currently opening or collapsing.
+ */
+ boolean isExpandingOrCollapsing();
+
+ /**
* Add a runnable for NotificationPanelView to post when the panel is expanded.
*
* @param action the action to post
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index c71467b..d00dab6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -164,6 +164,11 @@
}
@Override
+ public boolean isExpandingOrCollapsing() {
+ return mNotificationPanelViewController.isExpandingOrCollapsing();
+ }
+
+ @Override
public void postOnShadeExpanded(Runnable executable) {
mNotificationPanelViewController.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 25073c1b..2b772e3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -253,6 +253,31 @@
)
}
+ fun logFlingExpands(
+ vel: Float,
+ vectorVel: Float,
+ interactionType: Int,
+ minVelocityPxPerSecond: Float,
+ expansionOverHalf: Boolean,
+ allowExpandForSmallExpansion: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ int1 = interactionType
+ long1 = vel.toLong()
+ long2 = vectorVel.toLong()
+ double1 = minVelocityPxPerSecond.toDouble()
+ bool1 = expansionOverHalf
+ bool2 = allowExpandForSmallExpansion
+ },
+ { "NPVC flingExpands called with vel: $long1, vectorVel: $long2, " +
+ "interactionType: $int1, minVelocityPxPerSecond: $double1 " +
+ "expansionOverHalf: $bool1, allowExpandForSmallExpansion: $bool2" }
+ )
+ }
+
fun flingQs(flingType: Int, isClick: Boolean) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index d5a9e95..f75047c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -48,7 +48,7 @@
fun expandToNotifications()
/** Returns whether the shade is expanding or collapsing itself or quick settings. */
- val isExpanding: Boolean
+ val isExpandingOrCollapsing: Boolean
/**
* Returns whether the shade height is greater than zero (i.e. partially or fully expanded),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 0ebcfa2..fc1e87a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -141,6 +141,7 @@
mCarrierTextManager = carrierTextManagerBuilder
.setShowAirplaneMode(false)
.setShowMissingSim(false)
+ .setDebugLocationString("Shade")
.build();
mCarrierConfigTracker = carrierConfigTracker;
mSlotIndexResolver = slotIndexResolver;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index dcae258..8a96a47 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockScreenSceneInteractor
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -33,11 +33,11 @@
@AssistedInject
constructor(
@Application private val applicationScope: CoroutineScope,
- lockScreenSceneInteractorFactory: LockScreenSceneInteractor.Factory,
+ lockscreenSceneInteractorFactory: LockscreenSceneInteractor.Factory,
@Assisted private val containerName: String,
) {
- private val lockScreenInteractor: LockScreenSceneInteractor =
- lockScreenSceneInteractorFactory.create(containerName)
+ private val lockScreenInteractor: LockscreenSceneInteractor =
+ lockscreenSceneInteractorFactory.create(containerName)
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -54,13 +54,13 @@
/** Notifies that some content in the shade was clicked. */
fun onContentClicked() {
- lockScreenInteractor.dismissLockScreen()
+ lockScreenInteractor.dismissLockscreen()
}
private fun upDestinationSceneKey(
isLocked: Boolean,
): SceneKey {
- return if (isLocked) SceneKey.LockScreen else SceneKey.Gone
+ return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
}
@AssistedFactory
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/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index 43f78c3..e5849c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -87,7 +87,8 @@
}
}
-class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
+/** open only for testing purposes. (See [FakeStatusEvent.kt]) */
+open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent {
override var contentDescription: String? = null
override val priority = 100
override var forceVisible = true
@@ -107,9 +108,9 @@
}
override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean {
- return other is PrivacyEvent &&
- (other.privacyItems != privacyItems ||
- other.contentDescription != contentDescription)
+ return other is PrivacyEvent && (other.privacyItems != privacyItems ||
+ other.contentDescription != contentDescription ||
+ (other.forceVisible && !forceVisible))
}
override fun updateFromEvent(other: StatusEvent?) {
@@ -122,5 +123,7 @@
privacyChip?.contentDescription = other.contentDescription
privacyChip?.privacyList = other.privacyItems
+
+ if (other.forceVisible) forceVisible = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index f7a4fea..0a18f2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -153,6 +153,7 @@
)
}
currentlyDisplayedEvent?.updateFromEvent(event)
+ if (event.forceVisible) hasPersistentDot = true
} else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) {
if (DEBUG) {
Log.d(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 18ee481..59fc387 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -67,7 +67,10 @@
fun getViewLabel(view: View): String =
nodes.values.firstOrNull { node -> node.view === view }?.label ?: view.toString()
- private fun detachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+ private fun detachChildren(
+ parentNode: ShadeNode,
+ specMap: Map<NodeController, NodeSpec>
+ ) = traceSection("detachChildren") {
val views = nodes.values.associateBy { it.view }
fun detachRecursively(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
val parentSpec = specMap[parentNode.controller]
@@ -124,7 +127,10 @@
}
}
- private fun attachChildren(parentNode: ShadeNode, specMap: Map<NodeController, NodeSpec>) {
+ private fun attachChildren(
+ parentNode: ShadeNode,
+ specMap: Map<NodeController, NodeSpec>
+ ): Unit = traceSection("attachChildren") {
val parentSpec = checkNotNull(specMap[parentNode.controller])
for ((index, childSpec) in parentSpec.children.withIndex()) {
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/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ae7c216..b0f3f59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -93,6 +93,12 @@
private boolean mAppearing;
private float mPulseHeight = MAX_PULSE_HEIGHT;
+ /**
+ * The ExpandableNotificationRow that is pulsing, or the one that was pulsing
+ * when the device started to transition from AOD to LockScreen.
+ */
+ private ExpandableNotificationRow mPulsingRow;
+
/** Fraction of lockscreen to shade animation (on lockscreen swipe down). */
private float mFractionToShade;
@@ -564,6 +570,19 @@
return mPulsing && entry.isAlerting();
}
+ public void setPulsingRow(ExpandableNotificationRow row) {
+ mPulsingRow = row;
+ }
+
+ /**
+ * @param row The row to check
+ * @return true if row is the pulsing row when the device started to transition from AOD to lock
+ * screen
+ */
+ public boolean isPulsingRow(ExpandableView row) {
+ return mPulsingRow == row;
+ }
+
public boolean isPanelTracking() {
return mPanelTracking;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 92d767a..6f1c378 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -548,7 +548,7 @@
ExpandableViewState viewState = view.getViewState();
viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
- final float expansionFraction = getExpansionFractionWithoutShelf(
+ float expansionFraction = getExpansionFractionWithoutShelf(
algorithmState, ambientState);
// Add gap between sections.
@@ -619,6 +619,11 @@
updateViewWithShelf(view, viewState, shelfStart);
}
}
+ // Avoid pulsing notification flicker during AOD to LS
+ // A pulsing notification is already expanded, no need to expand it again with animation
+ if (ambientState.isPulsingRow(view)) {
+ expansionFraction = 1.0f;
+ }
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
@@ -700,9 +705,11 @@
&& !(child instanceof FooterView);
}
- private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
+ @VisibleForTesting
+ void updatePulsingStates(StackScrollAlgorithmState algorithmState,
AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
+ ExpandableNotificationRow pulsingRow = null;
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
if (!(child instanceof ExpandableNotificationRow)) {
@@ -714,6 +721,19 @@
}
ExpandableViewState viewState = row.getViewState();
viewState.hidden = false;
+ pulsingRow = row;
+ }
+
+ // Set AmbientState#pulsingRow to the current pulsing row when on AOD.
+ // Set AmbientState#pulsingRow=null when on lockscreen, since AmbientState#pulsingRow
+ // is only used for skipping the unfurl animation for (the notification that was already
+ // showing at full height on AOD) during the AOD=>lockscreen transition, where
+ // dozeAmount=[1f, 0f). We also need to reset the pulsingRow once it is no longer used
+ // because it will interfere with future unfurling animations - for example, during the
+ // LS=>AOD animation, the pulsingRow may stay at full height when it should squish with the
+ // rest of the stack.
+ if (ambientState.getDozeAmount() == 0.0f || ambientState.getDozeAmount() == 1.0f) {
+ ambientState.setPulsingRow(pulsingRow);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 37e77766..0ccc819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -339,7 +339,7 @@
mHeadsUpManager.unpinAll(true /* userUnpinned */);
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
- && !mShadeViewController.isExpanding()) {
+ && !mShadeViewController.isExpandingOrCollapsing()) {
mQsController.flingQs(0 /* velocity */,
ShadeViewController.FLING_EXPAND);
mMetricsLogger.count("panel_open_qs", 1);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 263566e..5c99f34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1224,6 +1224,7 @@
// By default turning off the screen also closes the shade.
// We want to make sure that the shade status is kept after folding/unfolding.
boolean isShadeOpen = mShadeController.isShadeFullyOpen();
+ boolean isShadeExpandingOrCollapsing = mShadeController.isExpandingOrCollapsing();
boolean leaveOpen = isShadeOpen && !willGoToSleep && mState == SHADE;
if (DEBUG) {
Log.d(TAG, String.format(
@@ -1231,14 +1232,15 @@
+ "isFolded=%s, "
+ "willGoToSleep=%s, "
+ "isShadeOpen=%s, "
+ + "isShadeExpandingOrCollapsing=%s, "
+ "leaveOpen=%s",
- isFolded, willGoToSleep, isShadeOpen, leaveOpen));
+ isFolded, willGoToSleep, isShadeOpen, isShadeExpandingOrCollapsing, leaveOpen));
}
if (leaveOpen) {
// below makes shade stay open when going from folded to unfolded
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
- if (mState != SHADE && isShadeOpen) {
+ if (mState != SHADE && (isShadeOpen || isShadeExpandingOrCollapsing)) {
// When device state changes on KEYGUARD/SHADE_LOCKED we don't want to keep the state of
// the shade and instead we open clean state of keyguard with shade closed.
// Normally some parts of QS state (like expanded/collapsed) are persisted and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f2fbd7d..414a2ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -488,7 +488,7 @@
final boolean hideBouncerOverDream =
mDreamOverlayStateController.isOverlayActive()
&& (mShadeViewController.isExpanded()
- || mShadeViewController.isExpanding());
+ || mShadeViewController.isExpandingOrCollapsing());
final boolean isUserTrackingStarted =
event.getFraction() != EXPANSION_HIDDEN && event.getTracking();
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/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
index 8e2b05c..b9c2487 100644
--- a/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
@@ -22,14 +22,21 @@
import android.net.Uri
import android.os.Bundle
import android.os.UserHandle
+import android.telecom.TelecomManager
import android.util.Log
import android.view.WindowManager
import com.android.internal.app.AlertActivity
import com.android.systemui.R
+import javax.inject.Inject
/** Dialog shown to the user to switch to managed profile for making a call using work SIM. */
-class SwitchToManagedProfileForCallActivity : AlertActivity(), DialogInterface.OnClickListener {
+class SwitchToManagedProfileForCallActivity
+@Inject
+constructor(
+ private val telecomManager: TelecomManager?,
+) : AlertActivity(), DialogInterface.OnClickListener {
private lateinit var phoneNumber: Uri
+ private lateinit var positiveActionIntent: Intent
private var managedProfileUserId = UserHandle.USER_NULL
override fun onCreate(savedInstanceState: Bundle?) {
@@ -37,8 +44,7 @@
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
)
super.onCreate(savedInstanceState)
-
- phoneNumber = intent.getData()
+ phoneNumber = intent.data ?: Uri.EMPTY
managedProfileUserId =
intent.getIntExtra(
"android.telecom.extra.MANAGED_PROFILE_USER_ID",
@@ -48,11 +54,31 @@
mAlertParams.apply {
mTitle = getString(R.string.call_from_work_profile_title)
mMessage = getString(R.string.call_from_work_profile_text)
- mPositiveButtonText = getString(R.string.call_from_work_profile_action)
mNegativeButtonText = getString(R.string.call_from_work_profile_close)
mPositiveButtonListener = this@SwitchToManagedProfileForCallActivity
mNegativeButtonListener = this@SwitchToManagedProfileForCallActivity
}
+
+ // A dialer app may not be available in the managed profile. We try to handle that
+ // gracefully by redirecting the user to the app market to install a suitable app.
+ val defaultDialerPackageName: String? =
+ telecomManager?.getDefaultDialerPackage(UserHandle.of(managedProfileUserId))
+
+ val (intent, positiveButtonText) =
+ defaultDialerPackageName?.let {
+ Intent(
+ Intent.ACTION_CALL,
+ phoneNumber,
+ ) to R.string.call_from_work_profile_action
+ }
+ ?: Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse(APP_STORE_DIALER_QUERY),
+ ) to R.string.install_dialer_on_work_profile_action
+
+ positiveActionIntent = intent
+ mAlertParams.apply { mPositiveButtonText = getString(positiveButtonText) }
+
setupAlert()
}
@@ -66,7 +92,7 @@
private fun switchToManagedProfile() {
try {
applicationContext.startActivityAsUser(
- Intent(Intent.ACTION_CALL, phoneNumber),
+ positiveActionIntent,
ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
UserHandle.of(managedProfileUserId)
)
@@ -77,5 +103,6 @@
companion object {
private const val TAG = "SwitchToManagedProfileForCallActivity"
+ private const val APP_STORE_DIALER_QUERY = "market://search?q=dialer"
}
}
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/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index 08dbb81..08b0c64 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -32,11 +32,9 @@
import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
-import com.android.settingslib.users.EditUserInfoController;
-import com.android.settingslib.users.GrantAdminDialogController;
+import com.android.settingslib.users.CreateUserDialogController;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.user.utils.MultiUserActionsEvent;
import javax.inject.Inject;
@@ -61,20 +59,18 @@
private static final String EXTRA_IS_KEYGUARD_SHOWING = "extra_is_keyguard_showing";
private final UserCreator mUserCreator;
- private final EditUserInfoController mEditUserInfoController;
+ private CreateUserDialogController mCreateUserDialogController;
private final IActivityManager mActivityManager;
private final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
- private Dialog mGrantAdminDialog;
private Dialog mSetupUserDialog;
private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
- private boolean mGrantAdminRights;
@Inject
public CreateUserActivity(UserCreator userCreator,
- EditUserInfoController editUserInfoController, IActivityManager activityManager,
+ CreateUserDialogController createUserDialogController, IActivityManager activityManager,
ActivityStarter activityStarter, UiEventLogger uiEventLogger) {
mUserCreator = userCreator;
- mEditUserInfoController = editUserInfoController;
+ mCreateUserDialogController = createUserDialogController;
mActivityManager = activityManager;
mActivityStarter = activityStarter;
mUiEventLogger = uiEventLogger;
@@ -86,19 +82,10 @@
setShowWhenLocked(true);
setContentView(R.layout.activity_create_new_user);
if (savedInstanceState != null) {
- mEditUserInfoController.onRestoreInstanceState(savedInstanceState);
+ mCreateUserDialogController.onRestoreInstanceState(savedInstanceState);
}
- boolean isKeyguardShowing = getIntent().getBooleanExtra(EXTRA_IS_KEYGUARD_SHOWING, true);
- // Display grant admin dialog only on unlocked device to admin users if multiple admins
- // are allowed on this device.
- if (mUserCreator.isMultipleAdminEnabled() && mUserCreator.isUserAdmin()
- && !isKeyguardShowing) {
- mGrantAdminDialog = buildGrantAdminDialog();
- mGrantAdminDialog.show();
- } else {
- mSetupUserDialog = createDialog();
- mSetupUserDialog.show();
- }
+ mSetupUserDialog = createDialog();
+ mSetupUserDialog.show();
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
mBackCallback);
@@ -110,7 +97,7 @@
outState.putBundle(DIALOG_STATE_KEY, mSetupUserDialog.onSaveInstanceState());
}
- mEditUserInfoController.onSaveInstanceState(outState);
+ mCreateUserDialogController.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@@ -125,48 +112,21 @@
private Dialog createDialog() {
String defaultUserName = getString(com.android.settingslib.R.string.user_new_user_name);
-
- return mEditUserInfoController.createDialog(
+ boolean isKeyguardShowing = getIntent().getBooleanExtra(EXTRA_IS_KEYGUARD_SHOWING, true);
+ return mCreateUserDialogController.createDialog(
this,
this::startActivity,
- null,
- defaultUserName,
- getString(com.android.settingslib.R.string.user_add_user),
+ (mUserCreator.isMultipleAdminEnabled() && mUserCreator.isUserAdmin()
+ && !isKeyguardShowing),
this::addUserNow,
this::finish
);
}
- /**
- * Returns dialog that allows to grant user admin rights.
- */
- private Dialog buildGrantAdminDialog() {
- return new GrantAdminDialogController().createDialog(
- this,
- (grantAdminRights) -> {
- mGrantAdminDialog.dismiss();
- mGrantAdminRights = grantAdminRights;
- if (mGrantAdminRights) {
- mUiEventLogger.log(MultiUserActionsEvent
- .GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG);
- } else {
- mUiEventLogger.log(MultiUserActionsEvent
- .NOT_GRANT_ADMIN_FROM_USER_SWITCHER_CREATION_DIALOG);
- }
- mSetupUserDialog = createDialog();
- mSetupUserDialog.show();
- },
- () -> {
- mGrantAdminRights = false;
- finish();
- }
- );
- }
-
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- mEditUserInfoController.onActivityResult(requestCode, resultCode, data);
+ mCreateUserDialogController.onActivityResult(requestCode, resultCode, data);
}
@Override
@@ -178,9 +138,6 @@
if (mSetupUserDialog != null) {
mSetupUserDialog.dismiss();
}
- if (mGrantAdminDialog != null) {
- mGrantAdminDialog.dismiss();
- }
finish();
}
@@ -190,7 +147,7 @@
super.onDestroy();
}
- private void addUserNow(String userName, Drawable userIcon) {
+ private void addUserNow(String userName, Drawable userIcon, Boolean isAdmin) {
mSetupUserDialog.dismiss();
userName = (userName == null || userName.trim().isEmpty())
? getString(com.android.settingslib.R.string.user_new_user_name)
@@ -198,7 +155,7 @@
mUserCreator.createUser(userName, userIcon,
userInfo -> {
- if (mGrantAdminRights) {
+ if (isAdmin) {
mUserCreator.setUserAdmin(userInfo.id);
}
switchToUser(userInfo.id);
@@ -230,7 +187,7 @@
*/
private void startActivity(Intent intent, int requestCode) {
mActivityStarter.dismissKeyguardThenExecute(() -> {
- mEditUserInfoController.startingActivityForResult();
+ mCreateUserDialogController.startingActivityForResult();
startActivityForResult(intent, requestCode);
return true;
}, /* cancel= */ null, /* afterKeyguardGone= */ true);
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index b2bf972..d8ee686 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -18,6 +18,7 @@
import android.os.UserHandle;
+import com.android.settingslib.users.CreateUserDialogController;
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.user.data.repository.UserRepositoryModule;
import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
@@ -45,6 +46,12 @@
return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
}
+ /** Provides {@link CreateUserDialogController} */
+ @Provides
+ public static CreateUserDialogController provideCreateUserDialogController() {
+ return new CreateUserDialogController(FILE_PROVIDER_AUTHORITY);
+ }
+
/**
* Provides the {@link UserHandle} for the user associated with this System UI process.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c2922c4..27c348b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -50,6 +50,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.CreateUserActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -455,13 +456,16 @@
UserActionModel.ADD_USER -> {
uiEventLogger.log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
val currentUser = repository.getSelectedUserInfo()
- showDialog(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = currentUser.userHandle,
- isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
- showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
- dialogShower = dialogShower,
- )
+ dismissDialog()
+ activityStarter.startActivity(
+ CreateUserActivity.createIntentForStart(
+ applicationContext,
+ keyguardInteractor.isKeyguardShowing()
+ ),
+ /* dismissShade= */ true,
+ /* animationController */ null,
+ /* showOverLockscreenWhenLocked */ true,
+ /* userHandle */ currentUser.getUserHandle(),
)
}
UserActionModel.ADD_SUPERVISED_USER -> {
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/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 30e3d09..b349696 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerGlobal;
import android.testing.AndroidTestingRunner;
@@ -38,6 +39,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -58,6 +60,10 @@
private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@Mock
private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
+ @Mock
+ private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
private Executor mMainExecutor = Runnable::run;
private Executor mBackgroundExecutor = Runnable::run;
@@ -76,7 +82,7 @@
MockitoAnnotations.initMocks(this);
mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
- mBackgroundExecutor));
+ mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController));
doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
@@ -123,4 +129,13 @@
mManager.show();
verify(mManager, times(1)).createPresentation(eq(mSecondaryDisplay));
}
+
+ @Test
+ public void testShow_concurrentDisplayActive_occluded() {
+ mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
+
+ when(mDeviceStateHelper.isConcurrentDisplayActive(mSecondaryDisplay)).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ verify(mManager, never()).createPresentation(eq(mSecondaryDisplay));
+ }
}
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..d2e5a45 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -18,6 +18,7 @@
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
+import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
import static com.google.common.truth.Truth.assertThat;
@@ -63,7 +64,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 +198,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,
@@ -685,6 +692,14 @@
verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
}
+ @Test
+ public void setExpansion_setsAlpha() {
+ mKeyguardSecurityContainerController.setExpansion(EXPANSION_VISIBLE);
+
+ verify(mView).setAlpha(1f);
+ verify(mView).setTranslationY(0f);
+ }
+
private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() {
mKeyguardSecurityContainerController.onViewAttached();
verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture());
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/MagnificationSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index 30cbc52..665246b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
-import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -74,10 +73,9 @@
@Test
public void testShowSettingsPanel() {
- final int mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
- mMagnificationSettingsController.showMagnificationSettings(mode);
+ mMagnificationSettingsController.showMagnificationSettings();
- verify(mWindowMagnificationSettings).showSettingPanel(eq(mode));
+ verify(mWindowMagnificationSettings).showSettingPanel();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index c08b5b4..ce96708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -16,7 +16,10 @@
package com.android.systemui.accessibility;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static com.google.common.truth.Truth.assertThat;
@@ -24,12 +27,17 @@
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.annotation.IdRes;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.database.ContentObserver;
+import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -102,7 +110,10 @@
@Test
public void showSettingPanel_hasAccessibilityWindowTitle() {
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
final WindowManager.LayoutParams layoutPrams =
mWindowManager.getLayoutParamsFromAttachedView();
@@ -114,7 +125,10 @@
@Test
public void showSettingPanel_windowMode_showEditButtonAndDiagonalView() {
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
final Button editButton = getInternalView(R.id.magnifier_edit_button);
assertEquals(editButton.getVisibility(), View.VISIBLE);
@@ -125,7 +139,10 @@
@Test
public void showSettingPanel_fullScreenMode_hideEditButtonAndDiagonalView() {
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ mWindowMagnificationSettings.showSettingPanel();
final Button editButton = getInternalView(R.id.magnifier_edit_button);
assertEquals(editButton.getVisibility(), View.INVISIBLE);
@@ -135,9 +152,22 @@
}
@Test
+ public void showSettingPanel_windowOnlyCapability_hideFullscreenButton() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
+
+ final View fullscreenButton = getInternalView(R.id.magnifier_full_button);
+ assertThat(fullscreenButton.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
public void performClick_smallSizeButton_changeMagnifierSizeSmallAndSwitchToWindowMode() {
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
verifyOnSetMagnifierSizeAndOnModeSwitch(
R.id.magnifier_small_button, MAGNIFICATION_SIZE_SMALL);
@@ -145,8 +175,10 @@
@Test
public void performClick_mediumSizeButton_changeMagnifierSizeMediumAndSwitchToWindowMode() {
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
verifyOnSetMagnifierSizeAndOnModeSwitch(
R.id.magnifier_medium_button, MAGNIFICATION_SIZE_MEDIUM);
@@ -154,8 +186,10 @@
@Test
public void performClick_largeSizeButton_changeMagnifierSizeLargeAndSwitchToWindowMode() {
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
verifyOnSetMagnifierSizeAndOnModeSwitch(
R.id.magnifier_large_button, MAGNIFICATION_SIZE_LARGE);
@@ -178,8 +212,10 @@
View fullScreenModeButton = getInternalView(R.id.magnifier_full_button);
getInternalView(R.id.magnifier_panel_view);
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
// Perform click
fullScreenModeButton.performClick();
@@ -192,8 +228,10 @@
public void performClick_editButton_setEditMagnifierSizeMode() {
View editButton = getInternalView(R.id.magnifier_edit_button);
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
// Perform click
editButton.performClick();
@@ -208,8 +246,10 @@
getInternalView(R.id.magnifier_horizontal_lock_switch);
final boolean currentCheckedState = diagonalScrollingSwitch.isChecked();
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
// Perform click
diagonalScrollingSwitch.performClick();
@@ -219,8 +259,10 @@
@Test
public void onConfigurationChanged_selectedButtonIsStillSelected() {
- // Open view
- mWindowMagnificationSettings.showSettingPanel(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mWindowMagnificationSettings.showSettingPanel();
View magnifierMediumButton = getInternalView(R.id.magnifier_medium_button);
magnifierMediumButton.performClick();
@@ -232,9 +274,46 @@
assertThat(magnifierMediumButton.isSelected()).isTrue();
}
+ @Test
+ public void showSettingsPanel_observerRegistered() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verify(mSecureSettings).registerContentObserverForUser(
+ eq(ACCESSIBILITY_MAGNIFICATION_CAPABILITY),
+ any(ContentObserver.class),
+ eq(UserHandle.USER_CURRENT));
+ }
+
+ @Test
+ public void hideSettingsPanel_observerUnregistered() {
+ setupMagnificationCapabilityAndMode(
+ /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL,
+ /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+
+ mWindowMagnificationSettings.showSettingPanel();
+ mWindowMagnificationSettings.hideSettingPanel();
+
+ verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class));
+ }
+
private <T extends View> T getInternalView(@IdRes int idRes) {
T view = mSettingView.findViewById(idRes);
assertNotNull(view);
return view;
}
+
+ private void setupMagnificationCapabilityAndMode(int capability, int mode) {
+ when(mSecureSettings.getIntForUser(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
+ ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ UserHandle.USER_CURRENT)).thenReturn(capability);
+ when(mSecureSettings.getIntForUser(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
+ ACCESSIBILITY_MAGNIFICATION_MODE_NONE,
+ UserHandle.USER_CURRENT)).thenReturn(mode);
+ }
}
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 239b5bd..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(
@@ -184,8 +198,9 @@
mWindowMagnification.mWindowMagnifierCallback.onClickSettingsButton(TEST_DISPLAY);
waitForIdleSync();
- verify(mMagnificationSettingsController).showMagnificationSettings(
- eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
+ verify(mMagnificationSettingsController).showMagnificationSettings();
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED));
}
@Test
@@ -196,6 +211,8 @@
waitForIdleSync();
verify(mWindowMagnificationController).changeMagnificationSize(eq(index));
+ verify(mA11yLogger).log(
+ eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED));
}
@Test
@@ -215,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
@@ -240,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));
}
@@ -269,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/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 1a620d2..fcd6568 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -70,7 +70,9 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.isNull
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -104,6 +106,9 @@
private lateinit var parent: FrameLayout
private lateinit var underTest: ControlsUiControllerImpl
+ private var isKeyguardDismissed: Boolean = true
+ private var isRemoveAppDialogCreated: Boolean = false
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -140,11 +145,23 @@
authorizedPanelsRepository,
preferredPanelRepository,
featureFlags,
- ControlsDialogsFactory { fakeDialogController.dialog },
+ ControlsDialogsFactory {
+ isRemoveAppDialogCreated = true
+ fakeDialogController.dialog
+ },
dumpManager,
)
`when`(userTracker.userId).thenReturn(0)
`when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
+ doAnswer {
+ if (isKeyguardDismissed) {
+ it.getArgument<ActivityStarter.OnDismissAction>(0).onDismiss()
+ } else {
+ it.getArgument<Runnable?>(1)?.run()
+ }
+ }
+ .whenever(activityStarter)
+ .dismissKeyguardThenExecute(any(), isNull(), any())
}
@Test
@@ -414,6 +431,26 @@
}
@Test
+ fun testKeyguardRemovingAppsNotShowingDialog() {
+ isKeyguardDismissed = false
+ val componentName = ComponentName(context, "cls")
+ whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
+ val panel = SelectedItem.PanelItem("App name", componentName)
+ preferredPanelRepository.setSelectedComponent(
+ SelectedComponentRepository.SelectedComponent(panel)
+ )
+ underTest.show(parent, {}, context)
+ underTest.startRemovingApp(componentName, "Test App")
+
+ assertThat(isRemoveAppDialogCreated).isFalse()
+ verify(controlsController, never()).removeFavorites(eq(componentName))
+ assertThat(underTest.getPreferredSelectedItem(emptyList())).isEqualTo(panel)
+ assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isTrue()
+ assertThat(preferredPanelRepository.getSelectedComponent())
+ .isEqualTo(SelectedComponentRepository.SelectedComponent(panel))
+ }
+
+ @Test
fun testCancelRemovingAppsDoesntRemoveFavorite() {
val componentName = ComponentName(context, "cls")
whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 0a9618c..688c2db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -114,7 +114,6 @@
@After
fun tearDown() {
keyguardUnlockAnimationController.notifyFinishedKeyguardExitAnimation(true)
- keyguardUnlockAnimationController.wallpaperAlphaAnimator.cancel()
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 489dc4d..f31ac00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -95,6 +95,7 @@
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import org.junit.Before;
import org.junit.Test;
@@ -135,6 +136,7 @@
private @Mock ScreenOffAnimationController mScreenOffAnimationController;
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
+ private @Mock KeyguardTransitions mKeyguardTransitions;
private @Mock ShadeController mShadeController;
private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
@@ -620,6 +622,7 @@
mScreenOffAnimationController,
() -> mNotificationShadeDepthController,
mScreenOnCoordinator,
+ mKeyguardTransitions,
mInteractionJankMonitor,
mDreamOverlayStateController,
() -> mShadeController,
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
deleted file mode 100644
index 749e7a0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockScreenSceneInteractorTest.kt
+++ /dev/null
@@ -1,270 +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.keyguard.domain.interactor
-
-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.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.runCurrent
-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 LockScreenSceneInteractorTest : SysuiTestCase() {
-
- private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
- )
- 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,
- )
- }
- },
- sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
- )
-
- @Test
- fun isDeviceLocked() =
- testScope.runTest {
- val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
-
- mAuthenticationInteractor.lockDevice()
- assertThat(isDeviceLocked).isTrue()
-
- mAuthenticationInteractor.unlockDevice()
- assertThat(isDeviceLocked).isFalse()
- }
-
- @Test
- fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
- testScope.runTest {
- val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
- assertThat(isSwipeToDismissEnabled).isTrue()
- }
-
- @Test
- fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
- testScope.runTest {
- val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
- mAuthenticationInteractor.unlockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
- assertThat(isSwipeToDismissEnabled).isFalse()
- }
-
- @Test
- fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- underTest.dismissLockScreen()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- }
-
- @Test
- fun dismissLockScreen_deviceUnlocked_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.unlockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- underTest.dismissLockScreen()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.lockDevice()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- underTest.dismissLockScreen()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
- runCurrent()
- mAuthenticationInteractor.unlockDevice()
- runCurrent()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-
- mAuthenticationInteractor.lockDevice()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
- }
-
- @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()
- }
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- mAuthenticationInteractor.biometricUnlock()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @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()
- }
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- mAuthenticationInteractor.biometricUnlock()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
- }
-
- @Test
- fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
- testScope.runTest {
- val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.LockScreen))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- assertThat(isUnlocked).isFalse()
-
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
-
- assertThat(isUnlocked).isTrue()
- }
-
- @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))
- assertThat(isUnlocked).isFalse()
-
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
-
- assertThat(isUnlocked).isFalse()
- }
-
- @Test
- fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
- testScope.runTest {
- val isUnlocked by collectLastValue(mAuthenticationInteractor.isUnlocked)
- runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Shade))
- runCurrent()
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- runCurrent()
- assertThat(isUnlocked).isFalse()
-
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.Gone))
-
- assertThat(isUnlocked).isFalse()
- }
-
- @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)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
-
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- runCurrent()
- sceneInteractor.setCurrentScene(CONTAINER_NAME, SceneModel(SceneKey.QuickSettings))
- runCurrent()
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
-
- mAuthenticationInteractor.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/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
new file mode 100644
index 0000000..d622f1c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.domain.interactor
+
+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.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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+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 LockscreenSceneInteractorTest : 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 underTest =
+ utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
+ )
+
+ @Test
+ fun isDeviceLocked() =
+ testScope.runTest {
+ val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
+
+ authenticationInteractor.lockDevice()
+ assertThat(isDeviceLocked).isTrue()
+
+ authenticationInteractor.unlockDevice()
+ assertThat(isDeviceLocked).isFalse()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isTrue()
+ }
+
+ @Test
+ fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
+ testScope.runTest {
+ val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
+
+ authenticationInteractor.unlockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+
+ assertThat(isSwipeToDismissEnabled).isFalse()
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ underTest.dismissLockscreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.unlockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ underTest.dismissLockscreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.lockDevice()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+ underTest.dismissLockscreen()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceLockedInNonLockScreenScene_switchesToLockScreenScene() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+ runCurrent()
+ authenticationInteractor.unlockDevice()
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+
+ authenticationInteractor.lockDevice()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassEnabled_switchesToGone() =
+ testScope.runTest {
+ 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))
+
+ authenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun deviceBiometricUnlockedInLockScreen_bypassNotEnabled_doesNotSwitch() =
+ testScope.runTest {
+ 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))
+
+ authenticationInteractor.biometricUnlock()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodSwipe_unlocksDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isTrue()
+ }
+
+ @Test
+ fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Lockscreen))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
+ testScope.runTest {
+ val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Shade))
+ runCurrent()
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ assertThat(isUnlocked).isFalse()
+
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.Gone))
+
+ assertThat(isUnlocked).isFalse()
+ }
+
+ @Test
+ fun authMethodChangedToNone_onLockScreenScene_dismissesLockScreen() =
+ testScope.runTest {
+ 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))
+
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ runCurrent()
+ sceneInteractor.setCurrentScene(CONTAINER_1, SceneModel(SceneKey.QuickSettings))
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.None)
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
+ }
+}
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..b5cb44a 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)
@@ -154,6 +159,7 @@
verify(repository).setPrimaryShow(false)
verify(mPrimaryBouncerCallbackInteractor).dispatchVisibilityChanged(View.INVISIBLE)
verify(repository).setPrimaryStartDisappearAnimation(null)
+ verify(repository).setPanelExpansion(EXPANSION_HIDDEN)
}
@Test
@@ -398,7 +404,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 +425,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
deleted file mode 100644
index d335b09..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockScreenSceneViewModelTest.kt
+++ /dev/null
@@ -1,190 +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.keyguard.ui.viewmodel
-
-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.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.runCurrent
-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 LockScreenSceneViewModelTest : SysuiTestCase() {
-
- private val testScope = TestScope()
- private val sceneInteractor =
- SceneInteractor(
- repository = fakeSceneContainerRepository(),
- )
- private val mAuthenticationInteractor =
- AuthenticationInteractor(
- applicationScope = testScope.backgroundScope,
- repository = AuthenticationRepositoryImpl(),
- )
-
- private val underTest =
- LockScreenSceneViewModel(
- applicationScope = testScope.backgroundScope,
- 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,
- )
- }
- },
- sceneInteractor = sceneInteractor,
- containerName = CONTAINER_NAME,
- )
- }
- },
- containerName = CONTAINER_NAME
- )
-
- @Test
- fun lockButtonIcon_whenLocked() =
- testScope.runTest {
- val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
- mAuthenticationInteractor.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
- )
- mAuthenticationInteractor.lockDevice()
-
- assertThat((lockButtonIcon as? Icon.Resource)?.res)
- .isEqualTo(R.drawable.ic_device_lock_on)
- }
-
- @Test
- fun lockButtonIcon_whenUnlocked() =
- testScope.runTest {
- val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
- mAuthenticationInteractor.setAuthenticationMethod(
- AuthenticationMethodModel.Password("password")
- )
- mAuthenticationInteractor.unlockDevice()
-
- assertThat((lockButtonIcon as? Icon.Resource)?.res)
- .isEqualTo(R.drawable.ic_device_lock_off)
- }
-
- @Test
- fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
- mAuthenticationInteractor.lockDevice()
-
- assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
- }
-
- @Test
- fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
- testScope.runTest {
- val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
-
- assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
- }
-
- @Test
- fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
- runCurrent()
-
- underTest.onLockButtonClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- }
-
- @Test
- fun onContentClicked_deviceUnlocked_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.unlockDevice()
- runCurrent()
-
- underTest.onContentClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
- }
-
- @Test
- fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.lockDevice()
- runCurrent()
-
- underTest.onContentClicked()
-
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
- }
-
- @Test
- fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_NAME))
- mAuthenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
- mAuthenticationInteractor.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/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
new file mode 100644
index 0000000..8ba3f0f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.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.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+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 LockscreenSceneViewModelTest : 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 underTest =
+ LockscreenSceneViewModel(
+ applicationScope = testScope.backgroundScope,
+ interactorFactory =
+ object : LockscreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockscreenSceneInteractor {
+ return utils.lockScreenSceneInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ bouncerInteractor =
+ utils.bouncerInteractor(
+ authenticationInteractor = authenticationInteractor,
+ sceneInteractor = sceneInteractor,
+ ),
+ )
+ }
+ },
+ containerName = CONTAINER_1
+ )
+
+ @Test
+ fun lockButtonIcon_whenLocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.lockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_on)
+ }
+
+ @Test
+ fun lockButtonIcon_whenUnlocked() =
+ testScope.runTest {
+ val lockButtonIcon by collectLastValue(underTest.lockButtonIcon)
+ authenticationInteractor.setAuthenticationMethod(
+ AuthenticationMethodModel.Password("password")
+ )
+ authenticationInteractor.unlockDevice()
+
+ assertThat((lockButtonIcon as? Icon.Resource)?.res)
+ .isEqualTo(R.drawable.ic_device_lock_off)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+ authenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
+ testScope.runTest {
+ val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onContentClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+
+ @Test
+ fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.lockDevice()
+ runCurrent()
+
+ underTest.onContentClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+ }
+
+ @Test
+ fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene(CONTAINER_1))
+ authenticationInteractor.setAuthenticationMethod(AuthenticationMethodModel.PIN(1234))
+ authenticationInteractor.unlockDevice()
+ runCurrent()
+
+ underTest.onLockButtonClicked()
+
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
+ }
+}
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 faca8a91d..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);
@@ -351,6 +283,8 @@
when(mMediaDevice1.isMutingExpectedDevice()).thenReturn(true);
when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+ mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+ .onCreateViewHolder(new LinearLayout(mContext), 0);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
@@ -446,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);
@@ -477,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);
@@ -503,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);
@@ -528,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);
@@ -600,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
@@ -626,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();
@@ -638,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);
@@ -662,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);
@@ -689,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(
@@ -707,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()));
@@ -764,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/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index bd7898a..c582cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -25,8 +25,11 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.ACTION_CREATE_NOTE
import android.content.Intent.ACTION_MAIN
+import android.content.Intent.ACTION_MANAGE_DEFAULT_APP
import android.content.Intent.CATEGORY_HOME
+import android.content.Intent.EXTRA_USE_STYLUS_MODE
import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@@ -47,8 +50,10 @@
import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
import com.android.systemui.notetask.NoteTaskEntryPoint.APP_CLIPS
+import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
@@ -221,31 +226,22 @@
// region showNoteTask
@Test
fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
- val expectedInfo =
- NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isKeyguardLocked = true,
- )
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
- createNoteTaskController()
- .showNoteTask(
- entryPoint = expectedInfo.entryPoint!!,
- )
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
- .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
- .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
- assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
@@ -256,32 +252,23 @@
fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
val user10 = UserHandle.of(/* userId= */ 10)
val expectedInfo =
- NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isKeyguardLocked = true,
- user = user10,
- )
+ NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
createNoteTaskController()
- .showNoteTaskAsUser(
- entryPoint = expectedInfo.entryPoint!!,
- user = user10,
- )
+ .showNoteTaskAsUser(entryPoint = expectedInfo.entryPoint!!, user = user10)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
- .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
- .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
- assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
assertThat(userCaptor.value).isEqualTo(user10)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
@@ -290,11 +277,7 @@
@Test
fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() {
- val expectedInfo =
- NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isKeyguardLocked = true,
- )
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
whenever(activityManager.getRunningTasks(anyInt()))
@@ -305,10 +288,10 @@
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(ACTION_MAIN)
- assertThat(intent.categories).contains(CATEGORY_HOME)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_MAIN)
+ categories().contains(CATEGORY_HOME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskClosed(expectedInfo)
@@ -317,18 +300,11 @@
@Test
fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
- val expectedInfo =
- NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isKeyguardLocked = false,
- )
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
- createNoteTaskController()
- .showNoteTask(
- entryPoint = expectedInfo.entryPoint!!,
- )
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
// Context package name used to create bubble icon from drawable resource id
verify(context).packageName
@@ -338,10 +314,7 @@
@Test
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
- createNoteTaskController(bubbles = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- )
+ createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
verifyZeroInteractions(context, bubbles, eventLogger)
}
@@ -352,7 +325,7 @@
val noteTaskController = spy(createNoteTaskController())
doNothing().whenever(noteTaskController).showNoDefaultNotesAppToast()
- noteTaskController.showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
+ noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON)
verify(noteTaskController).showNoDefaultNotesAppToast()
verifyZeroInteractions(context, bubbles, eventLogger)
@@ -360,10 +333,7 @@
@Test
fun showNoteTask_flagDisabled_shouldDoNothing() {
- createNoteTaskController(isEnabled = false)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- )
+ createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON)
verifyZeroInteractions(context, bubbles, eventLogger)
}
@@ -372,10 +342,7 @@
fun showNoteTask_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- createNoteTaskController()
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- )
+ createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
verifyZeroInteractions(context, bubbles, eventLogger)
}
@@ -383,30 +350,22 @@
@Test
fun showNoteTask_keyboardShortcut_shouldStartActivity() {
val expectedInfo =
- NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT,
- isKeyguardLocked = true,
- )
+ NOTE_TASK_INFO.copy(entryPoint = KEYBOARD_SHORTCUT, isKeyguardLocked = true)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
- createNoteTaskController()
- .showNoteTask(
- entryPoint = expectedInfo.entryPoint!!,
- )
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
- .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
- .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
- assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, true)).isFalse()
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isFalse()
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
@@ -583,7 +542,7 @@
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
- createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON)
+ createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
verifyNoteTaskOpenInBubbleInUser(workUserInfo.userHandle)
}
@@ -593,8 +552,7 @@
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
- createNoteTaskController()
- .showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
+ createNoteTaskController().showNoteTask(entryPoint = WIDGET_PICKER_SHORTCUT)
verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
}
@@ -604,7 +562,7 @@
whenever(devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile).thenReturn(true)
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo))
- createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.APP_CLIPS)
+ createNoteTaskController().showNoteTask(entryPoint = APP_CLIPS)
verifyNoteTaskOpenInBubbleInUser(mainUserInfo.userHandle)
}
@@ -615,13 +573,13 @@
val iconCaptor = argumentCaptor<Icon>()
verify(bubbles)
.showOrHideAppBubble(capture(intentCaptor), eq(userHandle), capture(iconCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
- iconCaptor.value.let { icon ->
+ iconCaptor.value?.let { icon ->
assertThat(icon).isNotNull()
assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
}
@@ -679,9 +637,10 @@
verify(shortcutManager).updateShortcuts(actualShortcuts.capture())
val actualShortcut = actualShortcuts.value.first()
assertThat(actualShortcut.id).isEqualTo(SHORTCUT_ID)
- assertThat(actualShortcut.intent?.component?.className)
- .isEqualTo(LaunchNoteTaskActivity::class.java.name)
- assertThat(actualShortcut.intent?.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+ assertThat(actualShortcut.intent).run {
+ hasComponentClass(LaunchNoteTaskActivity::class.java)
+ hasAction(ACTION_CREATE_NOTE)
+ }
assertThat(actualShortcut.shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
assertThat(actualShortcut.isLongLived).isEqualTo(true)
assertThat(actualShortcut.icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
@@ -737,12 +696,9 @@
val intentCaptor = argumentCaptor<Intent>()
verify(context).startActivityAsUser(intentCaptor.capture(), eq(user0))
- intentCaptor.value.let { intent ->
- assertThat(intent)
- .hasComponent(
- ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
- )
- assertThat(intent).hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intentCaptor.value).run {
+ hasComponentClass(LaunchNoteTaskManagedProfileProxyActivity::class.java)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
}
}
// endregion
@@ -817,9 +773,7 @@
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
- }
+ assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP)
assertThat(userCaptor.value).isEqualTo(UserHandle.of(workUserInfo.id))
}
@@ -833,9 +787,7 @@
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
- }
+ assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP)
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@@ -848,9 +800,7 @@
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
- }
+ assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP)
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
@@ -863,9 +813,7 @@
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent).hasAction(Intent.ACTION_MANAGE_DEFAULT_APP)
- }
+ assertThat(intentCaptor.value).hasAction(ACTION_MANAGE_DEFAULT_APP)
assertThat(userCaptor.value).isEqualTo(UserHandle.of(mainUserInfo.id))
}
// endregion
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 35c8cc7..8789253 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -28,8 +28,11 @@
import static org.mockito.Mockito.verify;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableLooper;
+import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.test.runner.AndroidJUnit4;
@@ -48,6 +51,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class TileLayoutTest extends SysuiTestCase {
private Resources mResources;
private int mLayoutSizeForOneTile;
@@ -228,4 +232,53 @@
assertEquals(false, mTileLayout.updateResources());
}
+
+ @Test
+ public void fontScalingChanged_updateResources_cellHeightEnoughForTileContent() {
+ final float originalFontScale = mContext.getResources().getConfiguration().fontScale;
+ float[] testScales = {0.8f, 1.0f, 1.4f, 1.6f, 2.0f};
+ for (float scale: testScales) {
+ changeFontScaling_updateResources_cellHeightEnoughForTileContent(scale);
+ }
+
+ changeFontScaling(originalFontScale);
+ }
+
+ private void changeFontScaling_updateResources_cellHeightEnoughForTileContent(float scale) {
+ changeFontScaling(scale);
+
+ QSPanelControllerBase.TileRecord tileRecord = createTileRecord();
+ mTileLayout.addTile(tileRecord);
+
+ FakeTileView tileView = new FakeTileView(mContext);
+ QSTile.State state = new QSTile.State();
+ state.label = "TEST LABEL";
+ state.secondaryLabel = "TEST SECONDARY LABEL";
+ tileView.changeState(state);
+
+ mTileLayout.updateResources();
+
+ int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ tileView.measure(spec, spec);
+ assertTrue(mTileLayout.getCellHeight() >= tileView.getMeasuredHeight());
+
+ mTileLayout.removeTile(tileRecord);
+ }
+
+ private static class FakeTileView extends QSTileViewImpl {
+ FakeTileView(Context context) {
+ super(context, new QSIconViewImpl(context), /* collapsed= */ false);
+ }
+
+ void changeState(QSTile.State state) {
+ handleStateChanged(state);
+ }
+ }
+
+ private void changeFontScaling(float scale) {
+ Configuration configuration = new Configuration(mContext.getResources().getConfiguration());
+ configuration.fontScale = scale;
+ // updateConfiguration could help update on both resource configuration and displayMetrics
+ mContext.getResources().updateConfiguration(configuration, null, null);
+ }
}
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..105387d 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.keyguard.domain.interactor.LockscreenSceneInteractor
+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,51 +40,38 @@
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 =
QuickSettingsSceneViewModel(
- 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,
- )
- }
- },
+ lockscreenSceneInteractorFactory =
+ object : LockscreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockscreenSceneInteractor {
+ 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..de15c77 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,15 +35,17 @@
@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(
SceneKey.QuickSettings,
SceneKey.Shade,
- SceneKey.LockScreen,
+ SceneKey.Lockscreen,
SceneKey.Bouncer,
SceneKey.Gone,
)
@@ -51,15 +54,15 @@
@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))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
@@ -67,25 +70,25 @@
@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)
+ 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..ee4f6c2 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,20 +35,18 @@
@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
fun sceneTransitions() = runTest {
val currentScene by collectLastValue(underTest.currentScene("container1"))
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
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..cd2f5af 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,13 +57,13 @@
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys).isEqualTo(fakeSceneKeys())
+ assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
}
@Test
fun sceneTransition() = runTest {
val currentScene by collectLastValue(underTest.currentScene)
- assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen))
+ assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
underTest.setCurrentScene(SceneModel(SceneKey.Shade))
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 3c08d58..27eec80 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -37,8 +37,8 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
+import android.os.Process;
import android.os.ResultReceiver;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.widget.ImageView;
@@ -120,7 +120,8 @@
ImageExporter.Result result = new ImageExporter.Result();
result.uri = TEST_URI;
when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class),
- any(UserHandle.class))).thenReturn(Futures.immediateFuture(result));
+ eq(Process.myUserHandle())))
+ .thenReturn(Futures.immediateFuture(result));
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index cbd9dba..e9007ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -27,6 +27,7 @@
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_USE_WP_USER;
import static com.google.common.truth.Truth.assertThat;
@@ -74,6 +75,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.Optional;
@RunWith(AndroidTestingRunner.class)
@@ -82,7 +84,6 @@
private static final String TEST_URI_STRING = "www.test-uri.com";
private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
private static final int TEST_UID = 42;
- private static final int TEST_USER_ID = 43;
private static final String TEST_CALLING_PACKAGE = "test-calling-package";
@Mock
@@ -287,6 +288,7 @@
assertThat(actualIntent.getComponent()).isEqualTo(
new ComponentName(mContext, AppClipsTrampolineActivity.class));
assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ assertThat(actualIntent.getBooleanExtra(EXTRA_USE_WP_USER, false)).isTrue();
assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
}
@@ -313,13 +315,13 @@
when(mOptionalBubbles.get()).thenReturn(mBubbles);
when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
- when(mUserTracker.getUserId()).thenReturn(TEST_USER_ID);
+ when(mUserTracker.getUserProfiles()).thenReturn(List.of());
ApplicationInfo testApplicationInfo = new ApplicationInfo();
testApplicationInfo.uid = TEST_UID;
when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
any(ApplicationInfoFlags.class),
- eq(TEST_USER_ID))).thenReturn(testApplicationInfo);
+ eq(mContext.getUser().getIdentifier()))).thenReturn(testApplicationInfo);
}
public static final class AppClipsTrampolineActivityTestable extends
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index e7c3c05..b7b8b11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -31,6 +31,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.net.Uri;
+import android.os.Process;
import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
@@ -57,10 +58,10 @@
private static final Drawable FAKE_DRAWABLE = new ShapeDrawable();
private static final Rect FAKE_RECT = new Rect();
private static final Uri FAKE_URI = Uri.parse("www.test-uri.com");
+ private static final UserHandle USER_HANDLE = Process.myUserHandle();
@Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@Mock private ImageExporter mImageExporter;
-
private AppClipsViewModel mViewModel;
@Before
@@ -99,10 +100,10 @@
@Test
public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() {
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
- any(UserHandle.class))).thenReturn(
+ eq(USER_HANDLE))).thenReturn(
Futures.immediateFailedFuture(new ExecutionException(new Throwable())));
- mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
waitForIdleSync();
assertThat(mViewModel.getErrorLiveData().getValue())
@@ -113,10 +114,9 @@
@Test
public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() {
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
- any(UserHandle.class))).thenReturn(
- Futures.immediateFuture(new ImageExporter.Result()));
+ eq(USER_HANDLE))).thenReturn(Futures.immediateFuture(new ImageExporter.Result()));
- mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
waitForIdleSync();
assertThat(mViewModel.getErrorLiveData().getValue())
@@ -129,9 +129,9 @@
ImageExporter.Result result = new ImageExporter.Result();
result.uri = FAKE_URI;
when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
- any(UserHandle.class))).thenReturn(Futures.immediateFuture(result));
+ eq(USER_HANDLE))).thenReturn(Futures.immediateFuture(result));
- mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+ mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT, USER_HANDLE);
waitForIdleSync();
assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 48e0b53..a5a9de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -902,6 +902,13 @@
}
@Test
+ public void isExpandingOrCollapsing_returnsTrue_whenQsLockscreenDragInProgress() {
+ when(mQsController.getLockscreenShadeDragProgress()).thenReturn(0.5f);
+ assertThat(mNotificationPanelViewController.isExpandingOrCollapsing()).isTrue();
+ }
+
+
+ @Test
public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() {
enableSplitShade(true);
mNotificationPanelViewController.expandToQs();
@@ -1099,7 +1106,7 @@
}
@Test
- public void shadeExpanded_inShadeState() {
+ public void shadeFullyExpanded_inShadeState() {
mStatusBarStateController.setState(SHADE);
mNotificationPanelViewController.setExpandedHeight(0);
@@ -1111,7 +1118,7 @@
}
@Test
- public void shadeExpanded_onKeyguard() {
+ public void shadeFullyExpanded_onKeyguard() {
mStatusBarStateController.setState(KEYGUARD);
int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
@@ -1120,8 +1127,39 @@
}
@Test
- public void shadeExpanded_onShadeLocked() {
+ public void shadeFullyExpanded_onShadeLocked() {
mStatusBarStateController.setState(SHADE_LOCKED);
assertThat(mNotificationPanelViewController.isShadeFullyExpanded()).isTrue();
}
+
+ @Test
+ public void shadeExpanded_whenHasHeight() {
+ int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+ mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void shadeExpanded_whenInstantExpanding() {
+ mNotificationPanelViewController.expand(true);
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void shadeExpanded_whenHunIsPresent() {
+ when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void shadeExpanded_whenWaitingForExpandGesture() {
+ mNotificationPanelViewController.startWaitingForExpandGesture();
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ }
+
+ @Test
+ public void shadeExpanded_whenUnlockedOffscreenAnimationRunning() {
+ when(mUnlockedScreenOffAnimationController.isAnimationPlaying()).thenReturn(true);
+ assertThat(mNotificationPanelViewController.isExpanded()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 2ef3d60..57ae621 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -109,6 +110,8 @@
.thenReturn(mCarrierTextControllerBuilder);
when(mCarrierTextControllerBuilder.setShowMissingSim(anyBoolean()))
.thenReturn(mCarrierTextControllerBuilder);
+ when(mCarrierTextControllerBuilder.setDebugLocationString(anyString()))
+ .thenReturn(mCarrierTextControllerBuilder);
when(mCarrierTextControllerBuilder.build()).thenReturn(mCarrierTextManager);
doAnswer(invocation -> mCallback = invocation.getArgument(0))
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..69d03d9 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.keyguard.domain.interactor.LockscreenSceneInteractor
+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,62 +40,49 @@
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 =
ShadeSceneViewModel(
applicationScope = testScope.backgroundScope,
- 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,
- )
- }
- },
+ lockscreenSceneInteractorFactory =
+ object : LockscreenSceneInteractor.Factory {
+ override fun create(containerName: String): LockscreenSceneInteractor {
+ 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)
+ assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@Test
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/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
index cd06465..8397702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -27,3 +27,9 @@
override val showAnimation: Boolean = true,
override var contentDescription: String? = "",
) : StatusEvent
+
+class FakePrivacyStatusEvent(
+ override val viewCreator: ViewCreator,
+ override val showAnimation: Boolean = true,
+ override var contentDescription: String? = "",
+) : PrivacyEvent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 08a9f31..39ed553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -380,6 +380,53 @@
}
@Test
+ fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringQueuedState() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule privacy event
+ createAndScheduleFakePrivacyEvent()
+ // request removal of persistent dot (sets forceVisible to false)
+ systemStatusAnimationScheduler.removePersistentDot()
+ // create and schedule a privacy event again (resets forceVisible to true)
+ createAndScheduleFakePrivacyEvent()
+
+ // skip chip animation lifecycle and fast forward to SHOWING_PERSISTENT_DOT state
+ fastForwardAnimationToState(SHOWING_PERSISTENT_DOT)
+
+ // verify that we reach SHOWING_PERSISTENT_DOT and that listener callback is invoked
+ assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+ }
+
+ @Test
+ fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringAnimatingState() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule privacy event
+ createAndScheduleFakePrivacyEvent()
+ // request removal of persistent dot (sets forceVisible to false)
+ systemStatusAnimationScheduler.removePersistentDot()
+ fastForwardAnimationToState(RUNNING_CHIP_ANIM)
+
+ // create and schedule a privacy event again (resets forceVisible to true)
+ createAndScheduleFakePrivacyEvent()
+
+ // skip status chip display time
+ advanceTimeBy(DISPLAY_LENGTH + 1)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemEventAnimationFinish(anyBoolean())
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+
+ // verify that we reach SHOWING_PERSISTENT_DOT and that listener callback is invoked
+ assertEquals(SHOWING_PERSISTENT_DOT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+ }
+
+ @Test
fun testNewEvent_isScheduled_whenPostedDuringRemovalAnimation() = runTest {
// Instantiate class under test with TestScope from runTest
initializeSystemStatusAnimationScheduler(testScope = this)
@@ -440,8 +487,7 @@
private fun createAndScheduleFakePrivacyEvent(): OngoingPrivacyChip {
val privacyChip = OngoingPrivacyChip(mContext)
- val fakePrivacyStatusEvent =
- FakeStatusEvent(viewCreator = { privacyChip }, priority = 100, forceVisible = true)
+ val fakePrivacyStatusEvent = FakePrivacyStatusEvent(viewCreator = { privacyChip })
systemStatusAnimationScheduler.onStatusEvent(fakePrivacyStatusEvent)
return privacyChip
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 7f20f1e..e12d179 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -716,6 +716,94 @@
.isLessThan(px(R.dimen.heads_up_pinned_elevation))
}
+ @Test
+ fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowDoesNotChange() {
+ // Given: Before AOD to LockScreen, there was a pulsing notification
+ val pulsingNotificationView = createPulsingViewMock()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(pulsingNotificationView)
+ ambientState.setPulsingRow(pulsingNotificationView)
+
+ // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
+ // stage; here we use 0.5 for testing.
+ // stackScrollAlgorithm.updatePulsingStates is called
+ ambientState.dozeAmount = 0.5f
+ stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+ // Then: ambientState.pulsingRow should still be pulsingNotificationView
+ assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+ }
+
+ @Test
+ fun deviceOnAod_hasPulsingNotification_recordPulsingNotificationRow() {
+ // Given: Device is on AOD, there is a pulsing notification
+ // ambientState.pulsingRow is null before stackScrollAlgorithm.updatePulsingStates
+ ambientState.dozeAmount = 1.0f
+ val pulsingNotificationView = createPulsingViewMock()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(pulsingNotificationView)
+ ambientState.setPulsingRow(null)
+
+ // When: stackScrollAlgorithm.updatePulsingStates is called
+ stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+ // Then: ambientState.pulsingRow should record the pulsingNotificationView
+ assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+ }
+
+ @Test
+ fun deviceOnLockScreen_hasPulsingNotificationBefore_clearPulsingNotificationRowRecord() {
+ // Given: Device finished AOD to LockScreen, there was a pulsing notification, and
+ // ambientState.pulsingRow was not null before AOD to LockScreen
+ // pulsingNotificationView.showingPulsing() returns false since the device is on LockScreen
+ ambientState.dozeAmount = 0.0f
+ val pulsingNotificationView = createPulsingViewMock()
+ whenever(pulsingNotificationView.showingPulsing()).thenReturn(false)
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(pulsingNotificationView)
+ ambientState.setPulsingRow(pulsingNotificationView)
+
+ // When: stackScrollAlgorithm.updatePulsingStates is called
+ stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
+
+ // Then: ambientState.pulsingRow should be null
+ assertTrue(ambientState.isPulsingRow(null))
+ }
+
+ @Test
+ fun aodToLockScreen_hasPulsingNotification_pulsingNotificationRowShowAtFullHeight() {
+ // Given: Before AOD to LockScreen, there was a pulsing notification
+ val pulsingNotificationView = createPulsingViewMock()
+ val algorithmState = StackScrollAlgorithm.StackScrollAlgorithmState()
+ algorithmState.visibleChildren.add(pulsingNotificationView)
+ ambientState.setPulsingRow(pulsingNotificationView)
+
+ // When: during AOD to LockScreen, any dozeAmount between (0, 1.0) is equivalent as a middle
+ // stage; here we use 0.5 for testing. The expansionFraction is also 0.5.
+ // stackScrollAlgorithm.resetViewStates is called.
+ ambientState.dozeAmount = 0.5f
+ setExpansionFractionWithoutShelfDuringAodToLockScreen(
+ ambientState,
+ algorithmState,
+ fraction = 0.5f
+ )
+ stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+ // Then: pulsingNotificationView should show at full height
+ assertEquals(
+ stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
+ pulsingNotificationView.viewState.height
+ )
+
+ // After: reset dozeAmount and expansionFraction
+ ambientState.dozeAmount = 0f
+ setExpansionFractionWithoutShelfDuringAodToLockScreen(
+ ambientState,
+ algorithmState,
+ fraction = 1f
+ )
+ }
+
private fun createHunViewMock(
isShadeOpen: Boolean,
fullyVisible: Boolean,
@@ -744,6 +832,29 @@
headsUpIsVisible = fullyVisible
}
+ private fun createPulsingViewMock(
+ ) =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(this.viewState).thenReturn(ExpandableViewState())
+ whenever(this.showingPulsing()).thenReturn(true)
+ }
+
+ private fun setExpansionFractionWithoutShelfDuringAodToLockScreen(
+ ambientState: AmbientState,
+ algorithmState: StackScrollAlgorithm.StackScrollAlgorithmState,
+ fraction: Float
+ ) {
+ // showingShelf: false
+ algorithmState.firstViewInShelf = null
+ // scrimPadding: 0, because device is on lock screen
+ ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+ ambientState.dozeAmount = 0.0f
+ // set stackEndHeight and stackHeight
+ // ExpansionFractionWithoutShelf == stackHeight / stackEndHeight
+ ambientState.stackEndHeight = 100f
+ ambientState.stackHeight = ambientState.stackEndHeight * fraction
+ }
+
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
expectedAlpha: Float,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c83769d..cf6d5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -335,6 +335,7 @@
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private final InitController mInitController = new InitController();
private final DumpManager mDumpManager = new DumpManager();
+ private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager);
@Before
public void setup() throws Exception {
@@ -487,7 +488,7 @@
mUserSwitcherController,
mBatteryController,
mColorExtractor,
- new ScreenLifecycle(mDumpManager),
+ mScreenLifecycle,
mWakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
@@ -554,6 +555,7 @@
return mViewRootImpl;
}
};
+ mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
when(mViewRootImpl.getOnBackInvokedDispatcher())
.thenReturn(mOnBackInvokedDispatcher);
@@ -1253,6 +1255,32 @@
}
@Test
+ public void deviceStateChange_unfolded_shadeExpanding_onKeyguard_closesQS() {
+ setFoldedStates(FOLD_STATE_FOLDED);
+ setGoToSleepStates(FOLD_STATE_FOLDED);
+ mCentralSurfaces.setBarStateForTest(KEYGUARD);
+ when(mNotificationPanelViewController.isExpandingOrCollapsing()).thenReturn(true);
+
+ setDeviceState(FOLD_STATE_UNFOLDED);
+ mScreenLifecycle.dispatchScreenTurnedOff();
+
+ verify(mQuickSettingsController).closeQs();
+ }
+
+ @Test
+ public void deviceStateChange_unfolded_shadeExpanded_onKeyguard_closesQS() {
+ setFoldedStates(FOLD_STATE_FOLDED);
+ setGoToSleepStates(FOLD_STATE_FOLDED);
+ mCentralSurfaces.setBarStateForTest(KEYGUARD);
+ when(mNotificationPanelViewController.isShadeFullyExpanded()).thenReturn(true);
+
+ setDeviceState(FOLD_STATE_UNFOLDED);
+ mScreenLifecycle.dispatchScreenTurnedOff();
+
+ verify(mQuickSettingsController).closeQs();
+ }
+
+ @Test
public void startActivityDismissingKeyguard_isShowingAndIsOccluded() {
when(mKeyguardStateController.isShowing()).thenReturn(true);
when(mKeyguardStateController.isOccluded()).thenReturn(true);
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/user/CreateUserActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
index 2b86cfd..6db35ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/CreateUserActivityTest.kt
@@ -6,6 +6,7 @@
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
@@ -27,9 +28,7 @@
createDialog(
/* activity = */ nullable(),
/* activityStarter = */ nullable(),
- /* oldUserIcon = */ nullable(),
- /* defaultUserName = */ nullable(),
- /* title = */ nullable(),
+ /* isMultipleAdminsEnabled = */ any(),
/* successCallback = */ nullable(),
/* cancelCallback = */ nullable()
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index d252d53..ca83d49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -482,30 +482,17 @@
}
@Test
- fun executeAction_addUser_dialogShown() =
+ fun executeAction_addUser_dismissesDialogAndStartsActivity() =
testScope.runTest {
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
keyguardRepository.setKeyguardShowing(false)
- val dialogRequest = collectLastValue(underTest.dialogShowRequests)
- val dialogShower: UserSwitchDialogController.DialogShower = mock()
- underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+ underTest.executeAction(UserActionModel.ADD_USER)
verify(uiEventLogger, times(1))
.log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
- assertThat(dialogRequest())
- .isEqualTo(
- ShowDialogRequestModel.ShowAddUserDialog(
- userHandle = userInfos[0].userHandle,
- isKeyguardShowing = false,
- showEphemeralMessage = false,
- dialogShower = dialogShower,
- )
- )
-
underTest.onDialogShown()
- assertThat(dialogRequest()).isNull()
}
@Test
@@ -862,7 +849,7 @@
// Dialog is shown.
assertThat(dialogRequest())
- .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
underTest.onDialogShown()
assertThat(dialogRequest()).isNull()
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..be3d54a
--- /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/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index a324b2f..e89e33f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -81,7 +81,6 @@
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -103,7 +102,6 @@
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.magnification.MagnificationProcessor;
import com.android.server.inputmethod.InputMethodManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -2081,7 +2079,7 @@
IAccessibilityInteractionConnectionCallback callback, int fetchFlags,
long interrogatingTid) {
RemoteAccessibilityConnection connection;
- IBinder activityToken = null;
+ IBinder windowToken = null;
synchronized (mLock) {
connection = mA11yWindowManager.getConnectionLocked(userId, resolvedWindowId);
if (connection == null) {
@@ -2090,9 +2088,8 @@
final boolean isA11yFocusAction = (action == ACTION_ACCESSIBILITY_FOCUS)
|| (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS);
if (!isA11yFocusAction) {
- final WindowInfo windowInfo =
- mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId);
- if (windowInfo != null) activityToken = windowInfo.activityToken;
+ windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
+ userId, resolvedWindowId);
}
final AccessibilityWindowInfo a11yWindowInfo =
mA11yWindowManager.findA11yWindowInfoByIdLocked(resolvedWindowId);
@@ -2113,9 +2110,8 @@
if (action == ACTION_CLICK || action == ACTION_LONG_CLICK) {
mA11yWindowManager.notifyOutsideTouch(userId, resolvedWindowId);
}
- if (activityToken != null) {
- LocalServices.getService(ActivityTaskManagerInternal.class)
- .setFocusedActivity(activityToken);
+ if (windowToken != null) {
+ mWindowManagerService.requestWindowFocus(windowToken);
}
if (intConnTracingEnabled()) {
logTraceIntConn("performAccessibilityAction",
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/RemoteFieldClassificationService.java b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
index b8bac61..ea31074 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFieldClassificationService.java
@@ -28,6 +28,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Build;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -155,7 +156,19 @@
public void onSuccess(FieldClassificationResponse response) {
logLatency(startTime);
if (sDebug) {
- Log.d(TAG, "onSuccess Response: " + response);
+ if (Build.IS_DEBUGGABLE) {
+ Slog.d(TAG, "onSuccess Response: " + response);
+ } else {
+ String msg = "";
+ if (response == null
+ || response.getClassifications() == null) {
+ msg = "null response";
+ } else {
+ msg = "size: "
+ + response.getClassifications().size();
+ }
+ Slog.d(TAG, "onSuccess " + msg);
+ }
}
fieldClassificationServiceCallbacks
.onClassificationRequestSuccess(response);
@@ -165,7 +178,7 @@
public void onFailure() {
logLatency(startTime);
if (sDebug) {
- Log.d(TAG, "onFailure");
+ Slog.d(TAG, "onFailure");
}
fieldClassificationServiceCallbacks
.onClassificationRequestFailure(0, null);
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 658e664..bfa397f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1175,17 +1175,21 @@
static final class StickyBroadcast {
public Intent intent;
public boolean deferUntilActive;
+ public int originalCallingUid;
- public static StickyBroadcast create(Intent intent, boolean deferUntilActive) {
+ public static StickyBroadcast create(Intent intent, boolean deferUntilActive,
+ int originalCallingUid) {
final StickyBroadcast b = new StickyBroadcast();
b.intent = intent;
b.deferUntilActive = deferUntilActive;
+ b.originalCallingUid = originalCallingUid;
return b;
}
@Override
public String toString() {
- return "{intent=" + intent + ", defer=" + deferUntilActive + "}";
+ return "{intent=" + intent + ", defer=" + deferUntilActive + ", originalCallingUid="
+ + originalCallingUid + "}";
}
}
@@ -11119,6 +11123,9 @@
pw.print(" [D]");
}
pw.println();
+ pw.print(" originalCallingUid: ");
+ pw.println(broadcasts.get(i).originalCallingUid);
+ pw.println();
Bundle bundle = intent.getExtras();
if (bundle != null) {
pw.print(" extras: ");
@@ -14008,16 +14015,25 @@
if (allSticky != null) {
ArrayList receivers = new ArrayList();
receivers.add(bf);
+ sticky = null;
final int stickyCount = allSticky.size();
for (int i = 0; i < stickyCount; i++) {
final StickyBroadcast broadcast = allSticky.get(i);
+ final int originalStickyCallingUid = allSticky.get(i).originalCallingUid;
+ // TODO(b/281889567): consider using checkComponentPermission instead of
+ // canAccessUnexportedComponents
+ if (sticky == null && (exported || originalStickyCallingUid == callingUid
+ || ActivityManager.canAccessUnexportedComponents(
+ originalStickyCallingUid))) {
+ sticky = broadcast.intent;
+ }
BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE,
BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
receivers, null, null, 0, null, null, false, true, true, -1,
- BackgroundStartPrivileges.NONE,
+ originalStickyCallingUid, BackgroundStartPrivileges.NONE,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
null /* filterExtrasForReceiver */);
queue.enqueueBroadcastLocked(r);
@@ -14895,12 +14911,13 @@
for (i = 0; i < stickiesCount; i++) {
if (intent.filterEquals(list.get(i).intent)) {
// This sticky already exists, replace it.
- list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive));
+ list.set(i, StickyBroadcast.create(new Intent(intent), deferUntilActive,
+ callingUid));
break;
}
}
if (i >= stickiesCount) {
- list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive));
+ list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive, callingUid));
}
}
@@ -18877,12 +18894,14 @@
@Override
public void waitForBroadcastIdle() {
- waitForBroadcastIdle(LOG_WRITER_INFO);
+ waitForBroadcastIdle(LOG_WRITER_INFO, false);
}
- public void waitForBroadcastIdle(@NonNull PrintWriter pw) {
+ void waitForBroadcastIdle(@NonNull PrintWriter pw, boolean flushBroadcastLoopers) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
- BroadcastLoopers.waitForIdle(pw);
+ if (flushBroadcastLoopers) {
+ BroadcastLoopers.waitForIdle(pw);
+ }
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForIdle(pw);
}
@@ -18895,7 +18914,7 @@
waitForBroadcastBarrier(LOG_WRITER_INFO, false, false);
}
- public void waitForBroadcastBarrier(@NonNull PrintWriter pw,
+ void waitForBroadcastBarrier(@NonNull PrintWriter pw,
boolean flushBroadcastLoopers, boolean flushApplicationThreads) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
if (flushBroadcastLoopers) {
@@ -18913,7 +18932,7 @@
* Wait for all pending {@link IApplicationThread} events to be processed in
* all currently running apps.
*/
- public void waitForApplicationBarrier(@NonNull PrintWriter pw) {
+ void waitForApplicationBarrier(@NonNull PrintWriter pw) {
final CountDownLatch finishedLatch = new CountDownLatch(1);
final AtomicInteger pingCount = new AtomicInteger(0);
final AtomicInteger pongCount = new AtomicInteger(0);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8759e3f..add22bd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -918,6 +918,7 @@
}
int runSendBroadcast(PrintWriter pw) throws RemoteException {
+ pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
Intent intent;
try {
intent = makeIntent(UserHandle.USER_CURRENT);
@@ -931,9 +932,10 @@
pw.println("Broadcasting: " + intent);
pw.flush();
Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle();
- mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null,
- requiredPermissions, null, null, android.app.AppOpsManager.OP_NONE, bundle, true,
- false, mUserId);
+ final int result = mInterface.broadcastIntentWithFeature(null, null, intent, null,
+ receiver, 0, null, null, requiredPermissions, null, null,
+ android.app.AppOpsManager.OP_NONE, bundle, true, false, mUserId);
+ Slogf.i(TAG, "Broadcasted %s: " + result, intent);
if (!mAsync) {
receiver.waitForFinish();
}
@@ -3445,7 +3447,17 @@
int runWaitForBroadcastIdle(PrintWriter pw) throws RemoteException {
pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
- mInternal.waitForBroadcastIdle(pw);
+ boolean flushBroadcastLoopers = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--flush-broadcast-loopers")) {
+ flushBroadcastLoopers = true;
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ mInternal.waitForBroadcastIdle(pw, flushBroadcastLoopers);
return 0;
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 87214de..030d596 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -247,6 +247,26 @@
private static final long DEFAULT_DELAY_URGENT_MILLIS = -120_000;
/**
+ * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to
+ * foreground processes, typically a negative value to indicate they should be
+ * executed before most other pending broadcasts.
+ */
+ public long DELAY_FOREGROUND_PROC_MILLIS = DEFAULT_DELAY_FOREGROUND_PROC_MILLIS;
+ private static final String KEY_DELAY_FOREGROUND_PROC_MILLIS =
+ "bcast_delay_foreground_proc_millis";
+ private static final long DEFAULT_DELAY_FOREGROUND_PROC_MILLIS = -120_000;
+
+ /**
+ * For {@link BroadcastQueueModernImpl}: Delay to apply to broadcasts to
+ * persistent processes, typically a negative value to indicate they should be
+ * executed before most other pending broadcasts.
+ */
+ public long DELAY_PERSISTENT_PROC_MILLIS = DEFAULT_DELAY_FOREGROUND_PROC_MILLIS;
+ private static final String KEY_DELAY_PERSISTENT_PROC_MILLIS =
+ "bcast_delay_persistent_proc_millis";
+ private static final long DEFAULT_DELAY_PERSISTENT_PROC_MILLIS = -120_000;
+
+ /**
* For {@link BroadcastQueueModernImpl}: Maximum number of complete
* historical broadcasts to retain for debugging purposes.
*/
@@ -411,6 +431,10 @@
DEFAULT_DELAY_CACHED_MILLIS);
DELAY_URGENT_MILLIS = getDeviceConfigLong(KEY_DELAY_URGENT_MILLIS,
DEFAULT_DELAY_URGENT_MILLIS);
+ DELAY_FOREGROUND_PROC_MILLIS = getDeviceConfigLong(KEY_DELAY_FOREGROUND_PROC_MILLIS,
+ DEFAULT_DELAY_FOREGROUND_PROC_MILLIS);
+ DELAY_PERSISTENT_PROC_MILLIS = getDeviceConfigLong(KEY_DELAY_PERSISTENT_PROC_MILLIS,
+ DEFAULT_DELAY_PERSISTENT_PROC_MILLIS);
MAX_HISTORY_COMPLETE_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_COMPLETE_SIZE,
DEFAULT_MAX_HISTORY_COMPLETE_SIZE);
MAX_HISTORY_SUMMARY_SIZE = getDeviceConfigInt(KEY_MAX_HISTORY_SUMMARY_SIZE,
@@ -463,6 +487,10 @@
TimeUtils.formatDuration(DELAY_CACHED_MILLIS)).println();
pw.print(KEY_DELAY_URGENT_MILLIS,
TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
+ pw.print(KEY_DELAY_FOREGROUND_PROC_MILLIS,
+ TimeUtils.formatDuration(DELAY_FOREGROUND_PROC_MILLIS)).println();
+ pw.print(KEY_DELAY_PERSISTENT_PROC_MILLIS,
+ TimeUtils.formatDuration(DELAY_PERSISTENT_PROC_MILLIS)).println();
pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 2803b4b..3ac2b2b 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1098,8 +1098,11 @@
mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
mRunnableAtReason = REASON_INSTRUMENTED;
} else if (mUidForeground) {
- mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS;
+ mRunnableAt = runnableAt + constants.DELAY_FOREGROUND_PROC_MILLIS;
mRunnableAtReason = REASON_FOREGROUND;
+ } else if (mProcessPersistent) {
+ mRunnableAt = runnableAt + constants.DELAY_PERSISTENT_PROC_MILLIS;
+ mRunnableAtReason = REASON_PERSISTENT;
} else if (mCountOrdered > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_ORDERED;
@@ -1112,9 +1115,6 @@
} else if (mCountManifest > 0) {
mRunnableAt = runnableAt;
mRunnableAtReason = REASON_CONTAINS_MANIFEST;
- } else if (mProcessPersistent) {
- mRunnableAt = runnableAt;
- mRunnableAtReason = REASON_PERSISTENT;
} else if (mUidCached) {
if (r.deferUntilActive) {
// All enqueued broadcasts are deferrable, defer
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index a9274408..a402db9 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -79,6 +79,9 @@
final @Nullable String callerFeatureId; // which feature in the package sent this
final int callingPid; // the pid of who sent this
final int callingUid; // the uid of who sent this
+
+ final int originalStickyCallingUid;
+ // if this is a sticky broadcast, the Uid of the original sender
final boolean callerInstantApp; // caller is an Instant App?
final boolean callerInstrumented; // caller is being instrumented?
final boolean ordered; // serialize the send to receivers?
@@ -330,7 +333,8 @@
pw.print(prefix); pw.print("resultAbort="); pw.print(resultAbort);
pw.print(" ordered="); pw.print(ordered);
pw.print(" sticky="); pw.print(sticky);
- pw.print(" initialSticky="); pw.println(initialSticky);
+ pw.print(" initialSticky="); pw.print(initialSticky);
+ pw.print(" originalStickyCallingUid="); pw.println(originalStickyCallingUid);
}
if (nextReceiver != 0) {
pw.print(prefix); pw.print("nextReceiver="); pw.println(nextReceiver);
@@ -399,6 +403,27 @@
}
}
+ BroadcastRecord(BroadcastQueue queue,
+ Intent intent, ProcessRecord callerApp, String callerPackage,
+ @Nullable String callerFeatureId, int callingPid, int callingUid,
+ boolean callerInstantApp, String resolvedType,
+ String[] requiredPermissions, String[] excludedPermissions,
+ String[] excludedPackages, int appOp,
+ BroadcastOptions options, List receivers,
+ ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode,
+ String resultData, Bundle resultExtras, boolean serialized, boolean sticky,
+ boolean initialSticky, int userId,
+ @NonNull BackgroundStartPrivileges backgroundStartPrivileges,
+ boolean timeoutExempt,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ this(queue, intent, callerApp, callerPackage, callerFeatureId, callingPid,
+ callingUid, callerInstantApp, resolvedType, requiredPermissions,
+ excludedPermissions, excludedPackages, appOp, options, receivers, resultToApp,
+ resultTo, resultCode, resultData, resultExtras, serialized, sticky,
+ initialSticky, userId, -1, backgroundStartPrivileges, timeoutExempt,
+ filterExtrasForReceiver);
+ }
+
BroadcastRecord(BroadcastQueue _queue,
Intent _intent, ProcessRecord _callerApp, String _callerPackage,
@Nullable String _callerFeatureId, int _callingPid, int _callingUid,
@@ -408,7 +433,7 @@
BroadcastOptions _options, List _receivers,
ProcessRecord _resultToApp, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
- boolean _initialSticky, int _userId,
+ boolean _initialSticky, int _userId, int originalStickyCallingUid,
@NonNull BackgroundStartPrivileges backgroundStartPrivileges,
boolean timeoutExempt,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
@@ -460,6 +485,7 @@
interactive = options != null && options.isInteractive();
shareIdentity = options != null && options.isShareIdentityEnabled();
this.filterExtrasForReceiver = filterExtrasForReceiver;
+ this.originalStickyCallingUid = originalStickyCallingUid;
}
/**
@@ -524,6 +550,7 @@
shareIdentity = from.shareIdentity;
urgent = from.urgent;
filterExtrasForReceiver = from.filterExtrasForReceiver;
+ originalStickyCallingUid = from.originalStickyCallingUid;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 6718319..5f918cf 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -563,14 +563,15 @@
// Ensure that broadcasts are only sent to other apps if they are explicitly marked as
// exported, or are System level broadcasts
+ final int originalCallingUid = r.sticky ? r.originalStickyCallingUid : r.callingUid;
if (!filter.exported && checkComponentPermission(null, r.callingPid,
- r.callingUid, filter.receiverList.uid, filter.exported)
+ originalCallingUid, filter.receiverList.uid, filter.exported)
!= PackageManager.PERMISSION_GRANTED) {
return "Exported Denial: sending "
+ r.intent.toString()
+ ", action: " + r.intent.getAction()
+ " from " + r.callerPackage
- + " (uid=" + r.callingUid + ")"
+ + " (uid=" + originalCallingUid + ")"
+ " due to receiver " + filter.receiverList.app
+ " (uid " + filter.receiverList.uid + ")"
+ " not specifying RECEIVER_EXPORTED";
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c393213..fbe7e70 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -107,6 +107,7 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
@@ -181,6 +182,8 @@
static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
"persist.sys.vold_app_data_isolation_enabled";
+ private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext";
+
// OOM adjustments for processes in various states:
// Uninitialized value for any major or minor adj fields
@@ -538,6 +541,78 @@
ActivityManagerGlobalLock mProcLock;
+ private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS =
+ "apply_sdk_sandbox_next_restrictions";
+ private static final boolean DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = false;
+
+ @GuardedBy("mService")
+ private ProcessListSettingsListener mProcessListSettingsListener;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ ProcessListSettingsListener getProcessListSettingsListener() {
+ synchronized (mService) {
+ if (mProcessListSettingsListener == null) {
+ mProcessListSettingsListener = new ProcessListSettingsListener(mService.mContext);
+ mProcessListSettingsListener.registerObserver();
+ }
+ return mProcessListSettingsListener;
+ }
+ }
+
+ static class ProcessListSettingsListener implements DeviceConfig.OnPropertiesChangedListener {
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mSdkSandboxApplyRestrictionsNext =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS,
+ DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS);
+
+ ProcessListSettingsListener(Context context) {
+ mContext = context;
+ }
+
+ private void registerObserver() {
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_ADSERVICES, mContext.getMainExecutor(), this);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ void unregisterObserver() {
+ DeviceConfig.removeOnPropertiesChangedListener(this);
+ }
+
+ boolean applySdkSandboxRestrictionsNext() {
+ synchronized (mLock) {
+ return mSdkSandboxApplyRestrictionsNext;
+ }
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ synchronized (mLock) {
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ continue;
+ }
+
+ switch (name) {
+ case PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS:
+ mSdkSandboxApplyRestrictionsNext =
+ properties.getBoolean(
+ PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS,
+ DEFAULT_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS);
+ break;
+ default:
+ }
+ }
+ }
+ }
+ }
+
final class IsolatedUidRange {
@VisibleForTesting
public final int mFirstUid;
@@ -1883,8 +1958,9 @@
new IllegalStateException("SELinux tag not defined for "
+ app.info.packageName + " (uid " + app.uid + ")"));
}
- final String seInfo = app.info.seInfo
- + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
+
+ String seInfo = updateSeInfo(app);
+
// Start the process. It will either succeed and return a result containing
// the PID of the new process, or else throw a RuntimeException.
final String entryPoint = "android.app.ActivityThread";
@@ -1907,6 +1983,21 @@
}
}
+ @VisibleForTesting
+ @GuardedBy("mService")
+ String updateSeInfo(ProcessRecord app) {
+ String extraInfo = "";
+ // By the time the first the SDK sandbox process is started, device config service
+ // should be available.
+ if (app.isSdkSandbox
+ && getProcessListSettingsListener().applySdkSandboxRestrictionsNext()) {
+ extraInfo = APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS;
+ }
+
+ return app.info.seInfo
+ + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser) + extraInfo;
+ }
+
@GuardedBy("mService")
boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index f520f6a..4dfd9b0 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -19,6 +19,7 @@
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT;
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager.opRestrictsRead;
import static android.app.AppOpsManager.opToDefaultMode;
@@ -41,6 +42,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.permission.PermissionManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -107,7 +109,7 @@
* {@link #upgradeLocked(int)} below. The first version was 1.
*/
@VisibleForTesting
- static final int CURRENT_VERSION = 3;
+ static final int CURRENT_VERSION = 4;
/**
* This stores the version of appops.xml seen at boot. If this is smaller than
@@ -1074,7 +1076,12 @@
upgradeScheduleExactAlarmLocked();
// fall through
case 2:
- // for future upgrades
+ // split the appops.xml into appops.xml to store appop state and apppops_access.xml
+ // to store app-op access.
+ // fall through
+ case 3:
+ resetUseFullScreenIntentLocked();
+ // fall through
}
scheduleFastWriteLocked();
}
@@ -1145,6 +1152,38 @@
}
}
+ /**
+ * A cleanup step for U Beta 2 that reverts the OP_USE_FULL_SCREEN_INTENT's mode to MODE_DEFAULT
+ * if the permission flags for the USE_FULL_SCREEN_INTENT permission does not have USER_SET.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void resetUseFullScreenIntentLocked() {
+ final PermissionManagerServiceInternal pmsi = LocalServices.getService(
+ PermissionManagerServiceInternal.class);
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ final PermissionManager permissionManager =
+ mContext.getSystemService(PermissionManager.class);
+
+ final String permissionName = AppOpsManager.opToPermission(OP_USE_FULL_SCREEN_INTENT);
+ final String[] packagesDeclaringPermission =
+ pmsi.getAppOpPermissionPackages(permissionName);
+ final int[] userIds = umi.getUserIds();
+
+ for (final String pkg : packagesDeclaringPermission) {
+ for (int userId : userIds) {
+ final int uid = pmi.getPackageUid(pkg, 0, userId);
+ final int flags = permissionManager.getPermissionFlags(pkg, permissionName,
+ UserHandle.of(userId));
+ if ((flags & PackageManager.FLAG_PERMISSION_USER_SET) == 0) {
+ setUidMode(uid, OP_USE_FULL_SCREEN_INTENT,
+ AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT));
+ }
+ }
+ }
+ }
+
@VisibleForTesting
List<Integer> getUidsWithNonDefaultModes() {
List<Integer> result = new ArrayList<>();
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 71401f4..f4c9d05 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -209,6 +209,7 @@
private void init() {
setupMessaging(mContext);
+ initAudioHalBluetoothState();
initRoutingStrategyIds();
mPreferredCommunicationDevice = null;
updateActiveCommunicationDevice();
@@ -864,21 +865,174 @@
}
}
- /**
- * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
- */
+ // Lock protecting state variable related to Bluetooth audio state
+ private final Object mBluetoothAudioStateLock = new Object();
+
+ // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
+ @GuardedBy("mBluetoothAudioStateLock")
private boolean mBluetoothScoOn;
+ // value of BT_SCO parameter currently applied to audio HAL.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothScoOnApplied;
+
+ // A2DP suspend state requested by AudioManager.setA2dpSuspended() API.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothA2dpSuspendedExt;
+ // A2DP suspend state requested by AudioDeviceInventory.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothA2dpSuspendedInt;
+ // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL.
+
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothA2dpSuspendedApplied;
+
+ // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothLeSuspendedExt;
+ // LE Audio suspend state requested by AudioDeviceInventory.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothLeSuspendedInt;
+ // value of LeAudioSuspended parameter currently applied to audio HAL.
+ @GuardedBy("mBluetoothAudioStateLock")
+ private boolean mBluetoothLeSuspendedApplied;
+
+ private void initAudioHalBluetoothState() {
+ synchronized (mBluetoothAudioStateLock) {
+ mBluetoothScoOnApplied = false;
+ AudioSystem.setParameters("BT_SCO=off");
+ mBluetoothA2dpSuspendedApplied = false;
+ AudioSystem.setParameters("A2dpSuspended=false");
+ mBluetoothLeSuspendedApplied = false;
+ AudioSystem.setParameters("LeAudioSuspended=false");
+ }
+ }
+
+ @GuardedBy("mBluetoothAudioStateLock")
+ private void updateAudioHalBluetoothState() {
+ if (mBluetoothScoOn != mBluetoothScoOnApplied) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: "
+ + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied);
+ }
+ if (mBluetoothScoOn) {
+ if (!mBluetoothA2dpSuspendedApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ mBluetoothA2dpSuspendedApplied = true;
+ }
+ if (!mBluetoothLeSuspendedApplied) {
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ mBluetoothLeSuspendedApplied = true;
+ }
+ AudioSystem.setParameters("BT_SCO=on");
+ } else {
+ AudioSystem.setParameters("BT_SCO=off");
+ }
+ mBluetoothScoOnApplied = mBluetoothScoOn;
+ }
+ if (!mBluetoothScoOnApplied) {
+ if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt)
+ != mBluetoothA2dpSuspendedApplied) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: "
+ + mBluetoothA2dpSuspendedExt
+ + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+ + ", mBluetoothA2dpSuspendedApplied: "
+ + mBluetoothA2dpSuspendedApplied);
+ }
+ mBluetoothA2dpSuspendedApplied =
+ mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt;
+ if (mBluetoothA2dpSuspendedApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ } else {
+ AudioSystem.setParameters("A2dpSuspended=false");
+ }
+ }
+ if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt)
+ != mBluetoothLeSuspendedApplied) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: "
+ + mBluetoothLeSuspendedExt
+ + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt
+ + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied);
+ }
+ mBluetoothLeSuspendedApplied =
+ mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt;
+ if (mBluetoothLeSuspendedApplied) {
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ } else {
+ AudioSystem.setParameters("LeAudioSuspended=false");
+ }
+ }
+ }
+ }
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
}
- synchronized (mDeviceStateLock) {
+ synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOn = on;
+ updateAudioHalBluetoothState();
postUpdateCommunicationRouteClient(eventSource);
}
}
+ /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) {
+ synchronized (mBluetoothAudioStateLock) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: "
+ + enable + ", internal: " + internal
+ + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt
+ + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+ }
+ if (internal) {
+ mBluetoothA2dpSuspendedInt = enable;
+ } else {
+ mBluetoothA2dpSuspendedExt = enable;
+ }
+ updateAudioHalBluetoothState();
+ }
+ }
+
+ /*package*/ void clearA2dpSuspended() {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "clearA2dpSuspended");
+ }
+ synchronized (mBluetoothAudioStateLock) {
+ mBluetoothA2dpSuspendedInt = false;
+ mBluetoothA2dpSuspendedExt = false;
+ updateAudioHalBluetoothState();
+ }
+ }
+
+ /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) {
+ synchronized (mBluetoothAudioStateLock) {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: "
+ + enable + ", internal: " + internal
+ + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt
+ + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt);
+ }
+ if (internal) {
+ mBluetoothLeSuspendedInt = enable;
+ } else {
+ mBluetoothLeSuspendedExt = enable;
+ }
+ updateAudioHalBluetoothState();
+ }
+ }
+
+ /*package*/ void clearLeAudioSuspended() {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "clearLeAudioSuspended");
+ }
+ synchronized (mBluetoothAudioStateLock) {
+ mBluetoothLeSuspendedInt = false;
+ mBluetoothLeSuspendedExt = false;
+ updateAudioHalBluetoothState();
+ }
+ }
+
/*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.startWatchingRoutes(observer);
@@ -1985,7 +2139,11 @@
*/
@GuardedBy("mDeviceStateLock")
@Nullable private AudioDeviceAttributes preferredCommunicationDevice() {
- boolean btSCoOn = mBluetoothScoOn && mBtHelper.isBluetoothScoOn();
+ boolean btSCoOn = mBtHelper.isBluetoothScoOn();
+ synchronized (mBluetoothAudioStateLock) {
+ btSCoOn = btSCoOn && mBluetoothScoOn;
+ }
+
if (btSCoOn) {
// Use the SCO device known to BtHelper so that it matches exactly
// what has been communicated to audio policy manager. The device
@@ -2020,12 +2178,6 @@
"updateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
- if (preferredCommunicationDevice == null
- || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
- AudioSystem.setParameters("BT_SCO=off");
- } else {
- AudioSystem.setParameters("BT_SCO=on");
- }
if (preferredCommunicationDevice == null) {
AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice();
if (defaultDevice != null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index b70c3e4..a561612 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) {
@@ -1478,7 +1479,7 @@
}
// Reset A2DP suspend state each time a new sink is connected
- mAudioSystem.setParameters("A2dpSuspended=false");
+ mDeviceBroker.clearA2dpSuspended();
// The convention for head tracking sensors associated with A2DP devices is to
// use a UUID derived from the MAC address as follows:
@@ -1751,7 +1752,8 @@
private void makeA2dpDeviceUnavailableLater(String address, int delayMs) {
// prevent any activity on the A2DP audio output to avoid unwanted
// reconnection of the sink.
- mAudioSystem.setParameters("A2dpSuspended=true");
+ mDeviceBroker.setA2dpSuspended(
+ true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater");
// retrieve DeviceInfo before removing device
final String deviceKey =
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
@@ -1898,7 +1900,7 @@
"LE Audio device addr=" + address + " now available").printLog(TAG));
}
// Reset LEA suspend state each time a new sink is connected
- mAudioSystem.setParameters("LeAudioSuspended=false");
+ mDeviceBroker.clearLeAudioSuspended();
UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
@@ -1953,7 +1955,8 @@
private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) {
// prevent any activity on the LEA output to avoid unwanted
// reconnection of the sink.
- mAudioSystem.setParameters("LeAudioSuspended=true");
+ mDeviceBroker.setLeAudioSuspended(
+ true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater");
// the device will be made unavailable later, so consider it disconnected right away
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
// send the delayed message to make the device unavailable later
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 24eba76..355981a 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
@@ -6412,6 +6424,26 @@
mDeviceBroker.setBluetoothScoOn(on, eventSource);
}
+ /** @see AudioManager#setA2dpSuspended(boolean) */
+ @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+ public void setA2dpSuspended(boolean enable) {
+ super.setA2dpSuspended_enforcePermission();
+ final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable)
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ mDeviceBroker.setA2dpSuspended(enable, false /*internal*/, eventSource);
+ }
+
+ /** @see AudioManager#setA2dpSuspended(boolean) */
+ @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK)
+ public void setLeAudioSuspended(boolean enable) {
+ super.setLeAudioSuspended_enforcePermission();
+ final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable)
+ .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
+ .append(Binder.getCallingPid()).toString();
+ mDeviceBroker.setLeAudioSuspended(enable, false /*internal*/, eventSource);
+ }
+
/** @see AudioManager#isBluetoothScoOn()
* Note that it doesn't report internal state, but state seen by apps (which may have
* called setBluetoothScoOn() */
@@ -11021,6 +11053,11 @@
dumpAccessibilityServiceUids(pw);
dumpAssistantServicesUids(pw);
+ pw.print(" supportsBluetoothVariableLatency=");
+ pw.println(AudioSystem.supportsBluetoothVariableLatency());
+ pw.print(" isBluetoothVariableLatencyEnabled=");
+ pw.println(AudioSystem.isBluetoothVariableLatencyEnabled());
+
dumpAudioPolicies(pw);
mDynPolicyLogger.dump(pw);
mPlaybackMonitor.dump(pw);
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/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index bfa6c36e..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -445,8 +445,8 @@
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- AudioSystem.setParameters("A2dpSuspended=false");
- AudioSystem.setParameters("LeAudioSuspended=false");
+ mDeviceBroker.clearA2dpSuspended();
+ mDeviceBroker.clearLeAudioSuspended();
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index bac4480..937e3f8 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -22,20 +22,14 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
-import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.IBinder;
import android.os.RemoteException;
-import android.text.TextUtils;
-import android.util.IndentingPrintWriter;
import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
-import java.util.List;
/**
* Wraps IBiometricAuthenticator implementation and stores information about the authenticator,
@@ -73,7 +67,6 @@
public final int id;
public final @Authenticators.Types int oemStrength; // strength as configured by the OEM
public final int modality;
- @NonNull public final List<ComponentInfoInternal> componentInfo;
public final IBiometricAuthenticator impl;
private @Authenticators.Types int mUpdatedStrength; // updated by BiometricStrengthController
@@ -93,16 +86,15 @@
*/
abstract boolean confirmationSupported();
- BiometricSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
- IBiometricAuthenticator impl) {
+ BiometricSensor(@NonNull Context context, int id, int modality,
+ @Authenticators.Types int strength, IBiometricAuthenticator impl) {
this.mContext = context;
- this.id = props.sensorId;
+ this.id = id;
this.modality = modality;
- this.oemStrength = Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
- this.componentInfo = Collections.unmodifiableList(props.componentInfo);
+ this.oemStrength = strength;
this.impl = impl;
- mUpdatedStrength = oemStrength;
+ mUpdatedStrength = strength;
goToStateUnknown();
}
@@ -186,25 +178,8 @@
return "ID(" + id + ")"
+ ", oemStrength: " + oemStrength
+ ", updatedStrength: " + mUpdatedStrength
- + ", modality: " + modality
+ + ", modality " + modality
+ ", state: " + mSensorState
+ ", cookie: " + mCookie;
}
-
- protected void dump(@NonNull IndentingPrintWriter pw) {
- pw.println(TextUtils.formatSimple("ID: %d", id));
- pw.increaseIndent();
- pw.println(TextUtils.formatSimple("oemStrength: %d", oemStrength));
- pw.println(TextUtils.formatSimple("updatedStrength: %d", mUpdatedStrength));
- pw.println(TextUtils.formatSimple("modality: %d", modality));
- pw.println("componentInfo:");
- for (ComponentInfoInternal info : componentInfo) {
- pw.increaseIndent();
- info.dump(pw);
- pw.decreaseIndent();
- }
- pw.println(TextUtils.formatSimple("state: %d", mSensorState));
- pw.println(TextUtils.formatSimple("cookie: %d", mCookie));
- pw.decreaseIndent();
- }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0ab74b8..4488434 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -64,7 +64,6 @@
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.ArraySet;
-import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -642,16 +641,13 @@
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public synchronized void registerAuthenticator(int modality,
- @NonNull SensorPropertiesInternal props,
+ public synchronized void registerAuthenticator(int id, int modality,
+ @Authenticators.Types int strength,
@NonNull IBiometricAuthenticator authenticator) {
super.registerAuthenticator_enforcePermission();
- @Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
-
- Slog.d(TAG, "Registering ID: " + props.sensorId
+ Slog.d(TAG, "Registering ID: " + id
+ " Modality: " + modality
+ " Strength: " + strength);
@@ -672,12 +668,12 @@
}
for (BiometricSensor sensor : mSensors) {
- if (sensor.id == props.sensorId) {
+ if (sensor.id == id) {
throw new IllegalStateException("Cannot register duplicate authenticator");
}
}
- mSensors.add(new BiometricSensor(getContext(), modality, props, authenticator) {
+ mSensors.add(new BiometricSensor(getContext(), id, modality, strength, authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return mSettingObserver.getConfirmationAlwaysRequired(modality, userId);
@@ -1376,17 +1372,13 @@
return null;
}
- private void dumpInternal(PrintWriter printWriter) {
- IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
-
+ private void dumpInternal(PrintWriter pw) {
pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings);
pw.println();
pw.println("Sensors:");
for (BiometricSensor sensor : mSensors) {
- pw.increaseIndent();
- sensor.dump(pw);
- pw.decreaseIndent();
+ pw.println(" " + sensor);
}
pw.println();
pw.println("CurrentSession: " + mAuthSession);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
index d43045b..0f0a81d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -27,6 +28,7 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BiometricServiceRegistry;
import java.util.List;
@@ -51,8 +53,10 @@
@Override
protected void registerService(@NonNull IBiometricService service,
@NonNull FaceSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
try {
- service.registerAuthenticator(TYPE_FACE, props,
+ service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
new FaceAuthenticator(mService, props.sensorId));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
index 6d210ea..33810b7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -27,6 +28,7 @@
import android.os.RemoteException;
import android.util.Slog;
+import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BiometricServiceRegistry;
import java.util.List;
@@ -51,8 +53,10 @@
@Override
protected void registerService(@NonNull IBiometricService service,
@NonNull FingerprintSensorPropertiesInternal props) {
+ @BiometricManager.Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
try {
- service.registerAuthenticator(TYPE_FINGERPRINT, props,
+ service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
new FingerprintAuthenticator(mService, props.sensorId));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index f27761f..35ea36c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -16,10 +16,12 @@
package com.android.server.biometrics.sensors.iris;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import android.annotation.NonNull;
import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.iris.IIrisService;
@@ -31,6 +33,7 @@
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import com.android.server.biometrics.Utils;
import java.util.List;
@@ -72,12 +75,17 @@
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
for (SensorPropertiesInternal hidlSensor : hidlSensors) {
+ final int sensorId = hidlSensor.sensorId;
+ final @BiometricManager.Authenticators.Types int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(
+ hidlSensor.sensorStrength);
+ final IrisAuthenticator authenticator = new IrisAuthenticator(mServiceWrapper,
+ sensorId);
try {
- biometricService.registerAuthenticator(TYPE_IRIS, hidlSensor,
- new IrisAuthenticator(mServiceWrapper, hidlSensor.sensorId));
+ biometricService.registerAuthenticator(sensorId, TYPE_IRIS, strength,
+ authenticator);
} catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: "
- + hidlSensor.sensorId);
+ Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
}
}
});
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 9645690..eb7fa10 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
@@ -73,6 +74,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
@@ -162,7 +164,7 @@
@GuardedBy("mLock")
private final SparseArray<ProcessRecord> mProcessRecords = new SparseArray<>();
- private Set<Integer> mDeviceStatesAvailableForAppRequests;
+ private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
private Set<Integer> mFoldedDeviceStates;
@@ -203,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
@@ -340,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);
}
@@ -389,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);
}
@@ -458,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);
}
@@ -591,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;
}
@@ -656,7 +664,7 @@
}
} else {
throw new IllegalArgumentException(
- "Unknown OverrideRest type: " + request.getRequestType());
+ "Unknown OverrideRest type: " + request.getRequestType());
}
boolean updatedPendingState = updatePendingStateLocked();
@@ -711,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) {
@@ -879,8 +890,16 @@
* @param callingPid Process ID that is requesting this state change
* @param state state that is being requested.
*/
- private void assertCanRequestDeviceState(int callingPid, int state) {
- if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) {
+ private void assertCanRequestDeviceState(int callingPid, int callingUid, int state) {
+ final boolean isTopApp = isTopApp(callingPid);
+ final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
+ final boolean isStateAvailableForAppRequests = isStateAvailableForAppRequests(state);
+
+ final boolean canRequestState = isTopApp
+ && isForegroundApp
+ && isStateAvailableForAppRequests;
+
+ if (!canRequestState) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app "
@@ -893,15 +912,43 @@
* not the top app, then check if this process holds the CONTROL_DEVICE_STATE permission.
*
* @param callingPid Process ID that is requesting this state change
+ * @param callingUid UID that is requesting this state change
*/
- private void assertCanControlDeviceState(int callingPid) {
- if (!isTopApp(callingPid)) {
+ private void assertCanControlDeviceState(int callingPid, int callingUid) {
+ final boolean isTopApp = isTopApp(callingPid);
+ final boolean isForegroundApp = isForegroundApp(callingPid, callingUid);
+
+ final boolean canControlState = isTopApp && isForegroundApp;
+
+ if (!canControlState) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app.");
}
}
+ /**
+ * Checks if the caller is in the foreground. Note that callers may be the top app as returned
+ * from {@link #isTopApp(int)}, but not be in the foreground. For example, keyguard may be on
+ * top of the top app.
+ */
+ private boolean isForegroundApp(int callingPid, int callingUid) {
+ try {
+ final List<ActivityManager.RunningAppProcessInfo> procs =
+ ActivityManager.getService().getRunningAppProcesses();
+ for (int i = 0; i < procs.size(); i++) {
+ ActivityManager.RunningAppProcessInfo proc = procs.get(i);
+ if (proc.pid == callingPid && proc.uid == callingUid
+ && proc.importance <= IMPORTANCE_FOREGROUND) {
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "am.getRunningAppProcesses() failed", e);
+ }
+ return false;
+ }
+
private boolean isTopApp(int callingPid) {
final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
return topApp != null && topApp.getPid() == callingPid;
@@ -918,7 +965,6 @@
*/
@GuardedBy("mLock")
private void readStatesAvailableForRequestFromApps() {
- mDeviceStatesAvailableForAppRequests = new HashSet<>();
String[] availableAppStatesConfigIdentifiers = getContext().getResources()
.getStringArray(R.array.config_deviceStatesAvailableForAppRequests);
for (int i = 0; i < availableAppStatesConfigIdentifiers.length; i++) {
@@ -1089,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) {
@@ -1118,7 +1165,7 @@
// Allow top processes to request a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- assertCanRequestDeviceState(callingPid, state);
+ assertCanRequestDeviceState(callingPid, callingUid, state);
if (token == null) {
throw new IllegalArgumentException("Request token must not be null.");
@@ -1139,10 +1186,11 @@
@Override // Binder call
public void cancelStateRequest() {
final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
// Allow top processes to cancel a device state change
// If the calling process ID is not the top app, then we check if this process
// holds a permission to CONTROL_DEVICE_STATE
- assertCanControlDeviceState(callingPid);
+ assertCanControlDeviceState(callingPid, callingUid);
final long callingIdentity = Binder.clearCallingIdentity();
try {
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/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index e5c50e6..4edc8bc 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -23,7 +23,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerInternal;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.SparseArray;
import android.view.Display;
@@ -333,6 +332,10 @@
return mPrimaryDisplayDevice != null;
}
+ boolean isDirtyLocked() {
+ return mDirty;
+ }
+
/**
* Updates the {@link DisplayGroup} to which the logical display belongs.
*
@@ -341,8 +344,7 @@
public void updateDisplayGroupIdLocked(int groupId) {
if (groupId != mDisplayGroupId) {
mDisplayGroupId = groupId;
- mBaseDisplayInfo.displayGroupId = groupId;
- mInfo.set(null);
+ mDirty = true;
}
}
@@ -932,18 +934,6 @@
return mDisplayGroupName;
}
- /**
- * Returns whether a display group other than the default display group needs to be assigned.
- *
- * <p>If display group name is empty or {@code Display.FLAG_OWN_DISPLAY_GROUP} is set, the
- * display is assigned to the default display group.
- */
- public boolean needsOwnDisplayGroupLocked() {
- DisplayInfo info = getDisplayInfoLocked();
- return (info.flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0
- || !TextUtils.isEmpty(mDisplayGroupName);
- }
-
public void dumpLocked(PrintWriter pw) {
pw.println("mDisplayId=" + mDisplayId);
pw.println("mIsEnabled=" + mIsEnabled);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 254441c2..d01b03f 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -679,7 +679,9 @@
for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {
final int displayId = mLogicalDisplays.keyAt(i);
LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ assignDisplayGroupLocked(display);
+ boolean wasDirty = display.isDirtyLocked();
mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
@@ -713,19 +715,14 @@
// The display is new.
} else if (!wasPreviouslyUpdated) {
Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
- assignDisplayGroupLocked(display);
mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
// Underlying displays device has changed to a different one.
} else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
- // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
- assignDisplayGroupLocked(display);
mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
// Something about the display device has changed.
- } else if (!mTempDisplayInfo.equals(newDisplayInfo)) {
- // FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in case
- assignDisplayGroupLocked(display);
+ } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
// If only the hdr/sdr ratio changed, then send just the event for that case
if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
mLogicalDisplaysToUpdate.put(displayId,
@@ -851,9 +848,18 @@
}
}
+ /** This method should be called before LogicalDisplay.updateLocked,
+ * DisplayInfo in LogicalDisplay (display.getDisplayInfoLocked()) is not updated yet,
+ * and should not be used directly or indirectly in this method */
private void assignDisplayGroupLocked(LogicalDisplay display) {
+ if (!display.isValidLocked()) { // null check for display.mPrimaryDisplayDevice
+ return;
+ }
+ // updated primary device directly from LogicalDisplay (not from DisplayInfo)
+ final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
+ // final in LogicalDisplay
final int displayId = display.getDisplayIdLocked();
- final String primaryDisplayUniqueId = display.getPrimaryDisplayDeviceLocked().getUniqueId();
+ final String primaryDisplayUniqueId = displayDevice.getUniqueId();
final Integer linkedDeviceUniqueId =
mVirtualDeviceDisplayMapping.get(primaryDisplayUniqueId);
@@ -866,8 +872,17 @@
}
final DisplayGroup oldGroup = getDisplayGroupLocked(groupId);
- // Get the new display group if a change is needed
- final boolean needsOwnDisplayGroup = display.needsOwnDisplayGroupLocked();
+ // groupName directly from LogicalDisplay (not from DisplayInfo)
+ final String groupName = display.getDisplayGroupNameLocked();
+ // DisplayDeviceInfo is safe to use, it is updated earlier
+ final DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+ // Get the new display group if a change is needed, if display group name is empty and
+ // {@code DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP} is not set, the display is assigned
+ // to the default display group.
+ final boolean needsOwnDisplayGroup =
+ (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
+ || !TextUtils.isEmpty(groupName);
+
final boolean hasOwnDisplayGroup = groupId != Display.DEFAULT_DISPLAY_GROUP;
final boolean needsDeviceDisplayGroup =
!needsOwnDisplayGroup && linkedDeviceUniqueId != null;
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/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index f37ad5e..ca1abd6 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1013,11 +1013,10 @@
}
@ServiceThreadOnly
- void addAvbAudioStatusAction(int targetAddress) {
+ void startNewAvbAudioStatusAction(int targetAddress) {
assertRunOnServiceThread();
- if (!hasAction(AbsoluteVolumeAudioStatusAction.class)) {
- addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
- }
+ removeAction(AbsoluteVolumeAudioStatusAction.class);
+ addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress));
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e87ed0a..cede273 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4341,12 +4341,9 @@
switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
case DeviceFeatures.FEATURE_SUPPORTED:
if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
- // If we're currently using adjust-only absolute volume behavior, switch to
- // full volume behavior until we successfully adopt absolute volume behavior
- switchToFullVolumeBehavior();
// Start an action that will call enableAbsoluteVolumeBehavior
// once the System Audio device sends <Report Audio Status>
- localCecDevice.addAvbAudioStatusAction(
+ localCecDevice.startNewAvbAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
return;
@@ -4358,10 +4355,13 @@
!= AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) {
// If we're currently using absolute volume behavior, switch to full volume
// behavior until we successfully adopt adjust-only absolute volume behavior
- switchToFullVolumeBehavior();
+ if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ getAudioManager().setDeviceVolumeBehavior(getAvbAudioOutputDevice(),
+ AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+ }
// Start an action that will call enableAbsoluteVolumeBehavior
// once the System Audio device sends <Report Audio Status>
- localCecDevice.addAvbAudioStatusAction(
+ localCecDevice.startNewAvbAudioStatusAction(
systemAudioDeviceInfo.getLogicalAddress());
}
} else {
@@ -4369,7 +4369,9 @@
}
return;
case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN:
- switchToFullVolumeBehavior();
+ if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
+ switchToFullVolumeBehavior();
+ }
localCecDevice.querySetAudioVolumeLevelSupport(
systemAudioDeviceInfo.getLogicalAddress());
}
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/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 8a28888..5b3f7a5 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -382,7 +382,11 @@
return filters;
}
- /** Call intent with tel scheme exclusively handled my managed profile. */
+ /** Call intent with tel scheme exclusively handled my managed profile.
+ * Note that work profile telephony relies on this intent filter to redirect intents to
+ * the IntentForwarderActivity. Work profile telephony error handling must be updated in
+ * the Telecomm package CallsManager if this filter is changed.
+ */
private static final DefaultCrossProfileIntentFilter CALL_MANAGED_PROFILE =
new DefaultCrossProfileIntentFilter.Builder(
DefaultCrossProfileIntentFilter.Direction.TO_PROFILE,
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/NoFilteringResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
index ccd5b0e..b87256d 100644
--- a/services/core/java/com/android/server/pm/NoFilteringResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -60,15 +60,9 @@
public static boolean isIntentRedirectionAllowed(Context context,
AppCloningDeviceConfigHelper appCloningDeviceConfigHelper, boolean resolveForStart,
long flags) {
- final long token = Binder.clearCallingIdentity();
- try {
- return context.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
- && appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks()
+ return isAppCloningBuildingBlocksEnabled(context, appCloningDeviceConfigHelper)
&& (resolveForStart || (((flags & PackageManager.MATCH_CLONE_PROFILE) != 0)
&& hasPermission(context, Manifest.permission.QUERY_CLONED_APPS)));
- } finally {
- Binder.restoreCallingIdentity(token);
- }
}
public NoFilteringResolver(ComponentResolverApi componentResolver,
@@ -146,4 +140,18 @@
return context.checkCallingOrSelfPermission(permission)
== PackageManager.PERMISSION_GRANTED;
}
+
+ /**
+ * Checks if the AppCloningBuildingBlocks flag is enabled.
+ */
+ private static boolean isAppCloningBuildingBlocksEnabled(Context context,
+ AppCloningDeviceConfigHelper appCloningDeviceConfigHelper) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return context.getResources().getBoolean(R.bool.config_enableAppCloningBuildingBlocks)
+ && appCloningDeviceConfigHelper.getEnableAppCloningBuildingBlocks();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
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/UserJourneyLogger.java b/services/core/java/com/android/server/pm/UserJourneyLogger.java
index f48a166..895edce 100644
--- a/services/core/java/com/android/server/pm/UserJourneyLogger.java
+++ b/services/core/java/com/android/server/pm/UserJourneyLogger.java
@@ -99,6 +99,8 @@
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__GRANT_ADMIN;
public static final int USER_JOURNEY_REVOKE_ADMIN =
FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__REVOKE_ADMIN;
+ public static final int USER_JOURNEY_USER_LIFECYCLE =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__JOURNEY__USER_LIFECYCLE;
@IntDef(prefix = {"USER_JOURNEY"}, value = {
USER_JOURNEY_UNKNOWN,
@@ -109,7 +111,8 @@
USER_JOURNEY_USER_CREATE,
USER_JOURNEY_USER_REMOVE,
USER_JOURNEY_GRANT_ADMIN,
- USER_JOURNEY_REVOKE_ADMIN
+ USER_JOURNEY_REVOKE_ADMIN,
+ USER_JOURNEY_USER_LIFECYCLE
})
public @interface UserJourney {
}
@@ -272,11 +275,12 @@
int userType, int userFlags, @UserJourneyErrorCode int errorCode) {
if (session == null) {
writeUserLifecycleJourneyReported(-1, journey, originalUserId, targetUserId,
- userType, userFlags, ERROR_CODE_INVALID_SESSION_ID);
+ userType, userFlags, ERROR_CODE_INVALID_SESSION_ID, -1);
} else {
+ final long elapsedTime = System.currentTimeMillis() - session.mStartTimeInMills;
writeUserLifecycleJourneyReported(
session.mSessionId, journey, originalUserId, targetUserId, userType, userFlags,
- errorCode);
+ errorCode, elapsedTime);
}
}
@@ -285,10 +289,10 @@
*/
@VisibleForTesting
public void writeUserLifecycleJourneyReported(long sessionId, int journey, int originalUserId,
- int targetUserId, int userType, int userFlags, int errorCode) {
+ int targetUserId, int userType, int userFlags, int errorCode, long elapsedTime) {
FrameworkStatsLog.write(FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED,
sessionId, journey, originalUserId, targetUserId, userType, userFlags,
- errorCode);
+ errorCode, elapsedTime);
}
/**
@@ -452,6 +456,29 @@
}
/**
+ * Log user journey event and report finishing with error
+ */
+ public UserJourneySession logDelayedUserJourneyFinishWithError(@UserIdInt int originalUserId,
+ UserInfo targetUser, @UserJourney int journey, @UserJourneyErrorCode int errorCode) {
+ synchronized (mLock) {
+ final int key = getUserJourneyKey(targetUser.id, journey);
+ final UserJourneySession userJourneySession = mUserIdToUserJourneyMap.get(key);
+ if (userJourneySession != null) {
+ logUserLifecycleJourneyReported(
+ userJourneySession,
+ journey, originalUserId, targetUser.id,
+ getUserTypeForStatsd(targetUser.userType),
+ targetUser.flags,
+ errorCode);
+ mUserIdToUserJourneyMap.remove(key);
+
+ return userJourneySession;
+ }
+ }
+ return null;
+ }
+
+ /**
* Log event and report finish when user is null. This is edge case when UserInfo
* can not be passed because it is null, therefore all information are passed as arguments.
*/
@@ -533,6 +560,23 @@
}
/**
+ * This keeps the start time when finishing extensively long journey was began.
+ * For instance full user lifecycle ( from creation to deletion )when user is about to delete
+ * we need to get user creation time before it was deleted.
+ */
+ public UserJourneySession startSessionForDelayedJourney(@UserIdInt int targetId,
+ @UserJourney int journey, long startTime) {
+ final long newSessionId = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
+ synchronized (mLock) {
+ final int key = getUserJourneyKey(targetId, journey);
+ final UserJourneySession userJourneySession =
+ new UserJourneySession(newSessionId, journey, startTime);
+ mUserIdToUserJourneyMap.append(key, userJourneySession);
+ return userJourneySession;
+ }
+ }
+
+ /**
* Helper class to store user journey and session id.
*
* <p> User journey tracks a chain of user lifecycle events occurring during different user
@@ -542,11 +586,19 @@
public final long mSessionId;
@UserJourney
public final int mJourney;
+ public long mStartTimeInMills;
@VisibleForTesting
public UserJourneySession(long sessionId, @UserJourney int journey) {
mJourney = journey;
mSessionId = sessionId;
+ mStartTimeInMills = System.currentTimeMillis();
+ }
+ @VisibleForTesting
+ public UserJourneySession(long sessionId, @UserJourney int journey, long startTimeInMills) {
+ mJourney = journey;
+ mSessionId = sessionId;
+ mStartTimeInMills = startTimeInMills;
}
}
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4ef68d8..4e043aa 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -30,6 +30,7 @@
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_REVOKE_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_LIFECYCLE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
import android.Manifest;
@@ -3332,7 +3333,7 @@
}
/**
- * Enforces that only the system UID or root's UID or apps that have the
+ * Enforces that only the system UID or root's UID or apps that have the
* {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
* {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}
* can make certain calls to the UserManager.
@@ -5535,6 +5536,8 @@
}
mUserJourneyLogger.logUserJourneyBegin(userId, USER_JOURNEY_USER_REMOVE);
+ mUserJourneyLogger.startSessionForDelayedJourney(userId,
+ USER_JOURNEY_USER_LIFECYCLE, userData.info.creationTime);
try {
mAppOpsService.removeUser(userId);
@@ -5560,6 +5563,10 @@
mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_UNSPECIFIED);
+ mUserJourneyLogger
+ .logDelayedUserJourneyFinishWithError(originUserId,
+ userData.info, USER_JOURNEY_USER_LIFECYCLE,
+ ERROR_CODE_UNSPECIFIED);
}
@Override
public void userStopAborted(int userIdParam) {
@@ -5567,6 +5574,10 @@
mUserJourneyLogger.logUserJourneyFinishWithError(originUserId,
userData.info, USER_JOURNEY_USER_REMOVE,
ERROR_CODE_ABORTED);
+ mUserJourneyLogger
+ .logDelayedUserJourneyFinishWithError(originUserId,
+ userData.info, USER_JOURNEY_USER_LIFECYCLE,
+ ERROR_CODE_ABORTED);
}
});
} catch (RemoteException 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/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 93d6676..4a57592a 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -1675,6 +1675,7 @@
String mLastWakeupReason = null;
long mLastWakeupUptimeMs = 0;
+ long mLastWakeupElapsedTimeMs = 0;
private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>();
public Map<String, ? extends Timer> getWakeupReasonStats() {
@@ -5048,7 +5049,7 @@
SamplingTimer timer = getWakeupReasonTimerLocked(mLastWakeupReason);
timer.add(deltaUptimeMs * 1000, 1, elapsedRealtimeMs); // time in in microseconds
FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_REPORTED, mLastWakeupReason,
- /* duration_usec */ deltaUptimeMs * 1000);
+ /* duration_usec */ deltaUptimeMs * 1000, mLastWakeupElapsedTimeMs);
mLastWakeupReason = null;
}
}
@@ -5059,6 +5060,7 @@
mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
mLastWakeupReason = reason;
mLastWakeupUptimeMs = uptimeMs;
+ mLastWakeupElapsedTimeMs = elapsedRealtimeMs;
}
@GuardedBy("this")
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4908529..9add537 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.
@@ -3982,6 +3987,8 @@
if (mFallbackWallpaper != null) {
dumpWallpaper(mFallbackWallpaper, pw);
}
+ pw.print("mIsLockscreenLiveWallpaperEnabled=");
+ pw.println(mIsLockscreenLiveWallpaperEnabled);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1e50f3d..0b7618d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -78,7 +78,6 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
-import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
@@ -94,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;
@@ -1299,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));
}
@@ -8128,7 +8128,7 @@
* aspect ratio.
*/
boolean shouldCreateCompatDisplayInsets() {
- switch (info.supportsSizeChanges()) {
+ switch (supportsSizeChanges()) {
case SIZE_CHANGES_SUPPORTED_METADATA:
case SIZE_CHANGES_SUPPORTED_OVERRIDE:
return false;
@@ -8155,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;
@@ -8446,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
@@ -8468,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
@@ -9323,8 +9352,7 @@
if (info.applicationInfo == null) {
return info.getMinAspectRatio();
}
-
- if (!info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO)) {
+ if (!mLetterboxUiController.shouldOverrideMinAspectRatio()) {
return info.getMinAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 9f738ed..e07c654 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -262,12 +262,6 @@
*/
public abstract void setVr2dDisplayId(int vr2dDisplayId);
- /**
- * Set focus on an activity.
- * @param token The activity token.
- */
- public abstract void setFocusedActivity(IBinder token);
-
public abstract void registerScreenObserver(ScreenObserver observer);
/**
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 48569f6..cff6554 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5818,26 +5818,12 @@
}
@Override
- public void setFocusedActivity(IBinder token) {
- synchronized (mGlobalLock) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (r == null) {
- throw new IllegalArgumentException(
- "setFocusedActivity: No activity record matching token=" + token);
- }
- if (r.moveFocusableActivityToTop("setFocusedActivity")) {
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- }
- }
-
- @Override
public int getDisplayId(IBinder token) {
synchronized (mGlobalLock) {
ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
throw new IllegalArgumentException(
- "setFocusedActivity: No activity record matching token=" + token);
+ "getDisplayId: No activity record matching token=" + token);
}
return r.getDisplayId();
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 778951a..98ee98b 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -248,7 +248,10 @@
Slog.e(TAG, "WM sent Transaction to organized, but never received" +
" commit callback. Application ANR likely to follow.");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- onCommitted(merged);
+ synchronized (mWm.mGlobalLock) {
+ onCommitted(merged.mNativeObject != 0
+ ? merged : mWm.mTransactionFactory.get());
+ }
}
};
CommitCallback callback = new CommitCallback();
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index 040da88..4da55e2 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -98,6 +98,13 @@
mSession == null ? null : mSession.getVirtualDisplayId());
incomingDisplayContent = wmService.mRoot.getDisplayContentOrCreate(
incomingSession.getVirtualDisplayId());
+ if (incomingDisplayContent == null) {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Incoming session on display %d can't be set since it "
+ + "is already null; the corresponding VirtualDisplay must have "
+ + "already been removed.", incomingSession.getVirtualDisplayId());
+ return;
+ }
incomingDisplayContent.setContentRecordingSession(incomingSession);
// TODO(b/270118861): When user grants consent to re-use, explicitly ask ContentRecorder
// to update, since no config/display change arrives. Mark recording as black.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fa5da30..251a087 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2962,6 +2962,7 @@
mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId,
mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight);
mDisplayRotation.physicalDisplayChanged();
+ mDisplayPolicy.physicalDisplayChanged();
}
// If there is an override set for base values - use it, otherwise use new values.
@@ -2993,6 +2994,7 @@
reconfigureDisplayLocked();
if (physicalDisplayChanged) {
+ mDisplayPolicy.physicalDisplayUpdated();
mDisplaySwitchTransitionLauncher.onDisplayUpdated(currentRotation, getRotation(),
getDisplayAreaInfo());
}
@@ -3042,7 +3044,7 @@
+ mBaseDisplayHeight + " on display:" + getDisplayId());
}
}
- if (mDisplayReady) {
+ if (mDisplayReady && !mDisplayPolicy.shouldKeepCurrentDecorInsets()) {
mDisplayPolicy.mDecorInsets.invalidate();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index db64b43..77e70a2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -174,6 +174,10 @@
private static final int INSETS_OVERRIDE_INDEX_INVALID = -1;
+ // TODO(b/266197298): Remove this by a more general protocol from the insets providers.
+ private static final boolean USE_CACHED_INSETS_FOR_DISPLAY_SWITCH =
+ SystemProperties.getBoolean("persist.wm.debug.cached_insets_switch", false);
+
private final WindowManagerService mService;
private final Context mContext;
private final Context mUiContext;
@@ -218,6 +222,8 @@
private SystemGesturesPointerEventListener mSystemGestures;
final DecorInsets mDecorInsets;
+ /** Currently it can only be non-null when physical display switch happens. */
+ private DecorInsets.Cache mCachedDecorInsets;
private volatile int mLidState = LID_ABSENT;
private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -1248,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
@@ -1707,11 +1717,7 @@
* Called when the configuration has changed, and it's safe to load new values from resources.
*/
public void onConfigurationChanged() {
- final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
-
final Resources res = getCurrentUserResources();
- final int portraitRotation = displayRotation.getPortraitRotation();
-
mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
@@ -1882,10 +1888,11 @@
@Override
public String toString() {
- return "{nonDecorInsets=" + mNonDecorInsets
- + ", configInsets=" + mConfigInsets
- + ", nonDecorFrame=" + mNonDecorFrame
- + ", configFrame=" + mConfigFrame + '}';
+ final StringBuilder tmpSb = new StringBuilder(32);
+ return "{nonDecorInsets=" + mNonDecorInsets.toShortString(tmpSb)
+ + ", configInsets=" + mConfigInsets.toShortString(tmpSb)
+ + ", nonDecorFrame=" + mNonDecorFrame.toShortString(tmpSb)
+ + ", configFrame=" + mConfigFrame.toShortString(tmpSb) + '}';
}
}
@@ -1923,6 +1930,39 @@
info.mNeedUpdate = true;
}
}
+
+ void setTo(DecorInsets src) {
+ for (int i = mInfoForRotation.length - 1; i >= 0; i--) {
+ mInfoForRotation[i].set(src.mInfoForRotation[i]);
+ }
+ }
+
+ void dump(String prefix, PrintWriter pw) {
+ for (int rotation = 0; rotation < mInfoForRotation.length; rotation++) {
+ final DecorInsets.Info info = mInfoForRotation[rotation];
+ pw.println(prefix + Surface.rotationToString(rotation) + "=" + info);
+ }
+ }
+
+ private static class Cache {
+ /**
+ * If {@link #mPreserveId} is this value, it is in the middle of updating display
+ * configuration before a transition is started. Then the active cache should be used.
+ */
+ static final int ID_UPDATING_CONFIG = -1;
+ final DecorInsets mDecorInsets;
+ int mPreserveId;
+ boolean mActive;
+
+ Cache(DisplayContent dc) {
+ mDecorInsets = new DecorInsets(dc);
+ }
+
+ boolean canPreserve() {
+ return mPreserveId == ID_UPDATING_CONFIG || mDecorInsets.mDisplayContent
+ .mTransitionController.inTransition(mPreserveId);
+ }
+ }
}
/**
@@ -1930,6 +1970,9 @@
* call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
*/
boolean updateDecorInsetsInfo() {
+ if (shouldKeepCurrentDecorInsets()) {
+ return false;
+ }
final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
final int rotation = displayFrames.mRotation;
final int dw = displayFrames.mWidth;
@@ -1940,6 +1983,10 @@
if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)) {
return false;
}
+ if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
+ && !mDisplayContent.isSleeping()) {
+ mCachedDecorInsets = null;
+ }
mDecorInsets.invalidate();
mDecorInsets.mInfoForRotation[rotation].set(newInfo);
return true;
@@ -1949,6 +1996,71 @@
return mDecorInsets.get(rotation, w, h);
}
+ /** Returns {@code true} to trust that {@link #mDecorInsets} already has the expected state. */
+ boolean shouldKeepCurrentDecorInsets() {
+ return mCachedDecorInsets != null && mCachedDecorInsets.mActive
+ && mCachedDecorInsets.canPreserve();
+ }
+
+ void physicalDisplayChanged() {
+ if (USE_CACHED_INSETS_FOR_DISPLAY_SWITCH) {
+ updateCachedDecorInsets();
+ }
+ }
+
+ /**
+ * Caches the current insets and switches current insets to previous cached insets. This is to
+ * reduce multiple display configuration changes if there are multiple insets provider windows
+ * which may trigger {@link #updateDecorInsetsInfo()} individually.
+ */
+ @VisibleForTesting
+ void updateCachedDecorInsets() {
+ DecorInsets prevCache = null;
+ if (mCachedDecorInsets == null) {
+ mCachedDecorInsets = new DecorInsets.Cache(mDisplayContent);
+ } else {
+ prevCache = new DecorInsets(mDisplayContent);
+ prevCache.setTo(mCachedDecorInsets.mDecorInsets);
+ }
+ // Set a special id to preserve it before a real id is available from transition.
+ mCachedDecorInsets.mPreserveId = DecorInsets.Cache.ID_UPDATING_CONFIG;
+ // Cache the current insets.
+ mCachedDecorInsets.mDecorInsets.setTo(mDecorInsets);
+ // Switch current to previous cache.
+ if (prevCache != null) {
+ mDecorInsets.setTo(prevCache);
+ mCachedDecorInsets.mActive = true;
+ }
+ }
+
+ /**
+ * Called after the display configuration is updated according to the physical change. Suppose
+ * there should be a display change transition, so associate the cached decor insets with the
+ * transition to limit the lifetime of using the cache.
+ */
+ void physicalDisplayUpdated() {
+ if (mCachedDecorInsets == null) {
+ return;
+ }
+ if (!mDisplayContent.mTransitionController.isCollecting()) {
+ // Unable to know when the display switch is finished.
+ mCachedDecorInsets = null;
+ return;
+ }
+ mCachedDecorInsets.mPreserveId =
+ mDisplayContent.mTransitionController.getCollectingTransitionId();
+ // The validator will run after the transition is finished. So if the insets are changed
+ // during the transition, it can update to the latest state.
+ mDisplayContent.mTransitionController.mStateValidators.add(() -> {
+ // The insets provider client may defer to change its window until screen is on. So
+ // only validate when awake to avoid the cache being always dropped.
+ if (!mDisplayContent.isSleeping() && updateDecorInsetsInfo()) {
+ Slog.d(TAG, "Insets changed after display switch transition");
+ mDisplayContent.sendNewConfiguration();
+ }
+ });
+ }
+
@NavigationBarPosition
int navigationBarPosition(int displayRotation) {
if (mNavigationBar != null) {
@@ -2626,9 +2738,10 @@
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
pw.println(mRemoteInsetsControllerControlsSystemBars);
pw.print(prefix); pw.println("mDecorInsetsInfo:");
- for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
- final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
- pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
+ mDecorInsets.dump(prefixInner, pw);
+ if (mCachedDecorInsets != null) {
+ pw.print(prefix); pw.println("mCachedDecorInsets:");
+ mCachedDecorInsets.mDecorInsets.dump(prefixInner, pw);
}
if (!CLIENT_TRANSIENT) {
mSystemGestures.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index 2b72215..1fbf593 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -245,7 +245,7 @@
}
/**
- * Notifies that animation in {@link ScreenAnimationRotation} has finished.
+ * Notifies that animation in {@link ScreenRotationAnimation} has finished.
*
* <p>This class uses this signal as a trigger for notifying the user about forced rotation
* reason with the {@link Toast}.
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 5b39790..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;
@@ -25,6 +27,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
@@ -49,7 +52,9 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
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;
@@ -185,10 +190,21 @@
// when dealing with translucent activities.
private final List<LetterboxUiController> mDestroyListeners = new ArrayList<>();
+ // 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;
@Nullable
private final Boolean mBooleanPropertyAllowDisplayOrientationOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowMinAspectRatioOverride;
+ @Nullable
+ private final Boolean mBooleanPropertyAllowForceResizeOverride;
/*
* WindowContainerListener responsible to make translucent activities inherit
@@ -300,6 +316,14 @@
readComponentProperty(packageManager, mActivityRecord.packageName,
/* gatingCondition */ null,
PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE);
+ mBooleanPropertyAllowMinAspectRatioOverride =
+ 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 =
@@ -330,6 +354,9 @@
mIsOverrideEnableCompatFakeFocusEnabled =
isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS);
+ mIsOverrideMinAspectRatio = isCompatChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO);
+ mIsOverrideForceResizeApp = isCompatChangeEnabled(FORCE_RESIZE_APP);
+ mIsOverrideForceNonResizeApp = isCompatChangeEnabled(FORCE_NON_RESIZE_APP);
}
/**
@@ -502,6 +529,61 @@
}
/**
+ * Whether we should apply the min aspect ratio per-app override. When this override is applied
+ * the min aspect ratio given in the app's manifest will be overridden to the largest enabled
+ * aspect ratio treatment unless the app's manifest value is higher. The treatment will also
+ * apply if no value is provided in the manifest.
+ *
+ * <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 shouldOverrideMinAspectRatio() {
+ return shouldEnableWithOptInOverrideAndOptOutProperty(
+ /* gatingCondition */ () -> true,
+ mIsOverrideMinAspectRatio,
+ mBooleanPropertyAllowMinAspectRatioOverride);
+ }
+
+ /**
+ * 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}.
*/
@@ -1280,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 5e60e921..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).
@@ -1194,6 +1208,13 @@
if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
asyncRotationController.onTransitionFinished();
}
+ if (hasParticipatedDisplay && dc.mDisplayRotationCompatPolicy != null) {
+ final ChangeInfo changeInfo = mChanges.get(dc);
+ if (changeInfo != null
+ && changeInfo.mRotation != dc.getWindowConfiguration().getRotation()) {
+ dc.mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished();
+ }
+ }
if (mTransientLaunches != null) {
InsetsControlTarget prevImeTarget = dc.getImeTarget(
DisplayContent.IME_TARGET_CONTROL);
@@ -1449,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 */);
}
@@ -1509,7 +1533,7 @@
mController.mLoggerHandler.post(mLogger::logOnSend);
if (mLogger.mInfo != null) {
- mController.mTransitionTracer.logSentTransition(this, mTargets, info);
+ mController.mTransitionTracer.logSentTransition(this, mTargets);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index b697ab1..1c6a412 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -431,6 +431,19 @@
return inCollectingTransition(wc) || inPlayingTransition(wc);
}
+ /** Returns {@code true} if the id matches a collecting or playing transition. */
+ boolean inTransition(int syncId) {
+ if (mCollectingTransition != null && mCollectingTransition.getSyncId() == syncId) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).getSyncId() == syncId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** @return {@code true} if wc is in a participant subtree */
boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
@@ -503,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/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index a002fba..8aa0cd6 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -29,7 +29,6 @@
import android.os.Trace;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import android.window.TransitionInfo;
import com.android.internal.util.TraceBuffer;
import com.android.server.wm.Transition.ChangeInfo;
@@ -69,26 +68,29 @@
*
* @param transition The transition that has been sent to Shell.
* @param targets Information about the target windows of the transition.
- * @param info The TransitionInfo send over to Shell to execute the transition.
*/
- public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets,
- TransitionInfo info) {
- final ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
- transition.mLogger.mCreateTimeNs);
- outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
- transition.mLogger.mSendTimeNs);
- outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
- transition.getStartTransaction().getId());
- outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
- transition.getFinishTransaction().getId());
- dumpTransitionTargetsToProto(outputStream, transition, targets);
- outputStream.end(protoToken);
+ public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets) {
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream();
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS,
+ transition.mLogger.mCreateTimeNs);
+ outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS,
+ transition.mLogger.mSendTimeNs);
+ outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID,
+ transition.getStartTransaction().getId());
+ outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID,
+ transition.getFinishTransaction().getId());
+ dumpTransitionTargetsToProto(outputStream, transition, targets);
+ outputStream.end(protoToken);
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
}
/**
@@ -98,15 +100,20 @@
* @param transition The transition that has finished.
*/
public void logFinishedTransition(Transition transition) {
- final ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
- transition.mLogger.mFinishTimeNs);
- outputStream.end(protoToken);
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream();
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS,
+ transition.mLogger.mFinishTimeNs);
+ outputStream.end(protoToken);
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
}
/**
@@ -116,15 +123,20 @@
* @param transition The transition that has been aborted
*/
public void logAbortedTransition(Transition transition) {
- final ProtoOutputStream outputStream = new ProtoOutputStream();
- final long protoToken = outputStream
- .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
- outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
- outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
- transition.mLogger.mAbortTimeNs);
- outputStream.end(protoToken);
+ try {
+ final ProtoOutputStream outputStream = new ProtoOutputStream();
+ final long protoToken = outputStream
+ .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+ outputStream.write(com.android.server.wm.shell.Transition.ID, transition.getSyncId());
+ outputStream.write(com.android.server.wm.shell.Transition.ABORT_TIME_NS,
+ transition.mLogger.mAbortTimeNs);
+ outputStream.end(protoToken);
- mTraceBuffer.add(outputStream);
+ mTraceBuffer.add(outputStream);
+ } catch (Exception e) {
+ // Don't let any errors in the tracing cause the transition to fail
+ Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+ }
}
private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
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/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 792ec2e..92d4cec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -449,6 +449,19 @@
public abstract void moveDisplayToTopIfAllowed(int displayId);
/**
+ * Request to move window input focus to the window with the provided window token.
+ *
+ * <p>
+ * It is necessary to move window input focus before certain actions on views in a window can
+ * be performed, such as opening an IME. Input normally requests to move focus on window touch
+ * so this method should not be necessary in most cases; only features that bypass normal touch
+ * behavior (like Accessibility actions) require this method.
+ * </p>
+ * @param windowToken The window token.
+ */
+ public abstract void requestWindowFocus(IBinder windowToken);
+
+ /**
* @return Whether the keyguard is engaged.
*/
public abstract boolean isKeyguardLocked();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7776132..322c11a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5652,7 +5652,7 @@
case ON_POINTER_DOWN_OUTSIDE_FOCUS: {
synchronized (mGlobalLock) {
final IBinder touchedToken = (IBinder) msg.obj;
- onPointerDownOutsideFocusLocked(touchedToken);
+ onPointerDownOutsideFocusLocked(getInputTargetFromToken(touchedToken));
}
break;
}
@@ -7752,6 +7752,15 @@
}
@Override
+ public void requestWindowFocus(IBinder windowToken) {
+ synchronized (mGlobalLock) {
+ final InputTarget inputTarget =
+ WindowManagerService.this.getInputTargetFromWindowTokenLocked(windowToken);
+ WindowManagerService.this.onPointerDownOutsideFocusLocked(inputTarget);
+ }
+ }
+
+ @Override
public boolean isKeyguardLocked() {
return WindowManagerService.this.isKeyguardLocked();
}
@@ -8590,8 +8599,7 @@
}
}
- private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
- InputTarget t = getInputTargetFromToken(touchedToken);
+ private void onPointerDownOutsideFocusLocked(InputTarget t) {
if (t == null || !t.receiveFocusFromTapOutside()) {
// If the window that received the input event cannot receive keys, don't move the
// display it's on to the top since that window won't be able to get focus anyway.
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/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index fe8a8c8..69a5e5c 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -93,6 +93,7 @@
public void onFinalResponseReceived(
ComponentName componentName,
Void response) {
+ mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.updateMetricsOnResponseReceived(mProviders, componentName,
isPrimaryProviderViaProviderInfo(componentName));
respondToClientWithResponseAndFinish(null);
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 20dd181..c37a038 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -64,7 +64,7 @@
RequestInfo.TYPE_CREATE,
callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp);
mRequestSessionMetric.collectCreateFlowInitialMetricInfo(
- /*origin=*/request.getOrigin() != null);
+ /*origin=*/request.getOrigin() != null, request);
mPrimaryProviders = primaryProviders;
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 25561ed..272452e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -33,7 +33,6 @@
import android.os.Looper;
import android.os.ResultReceiver;
import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
@@ -72,7 +71,6 @@
};
private void handleUiResult(int resultCode, Bundle resultData) {
- Log.i("reemademo", "handleUiResult with resultCOde: " + resultCode);
switch (resultCode) {
case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION:
@@ -86,13 +84,11 @@
}
break;
case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED:
- Log.i("reemademo", "RESULT_CODE_DIALOG_USER_CANCELED");
mStatus = UiStatus.TERMINATED;
mCallbacks.onUiCancellation(/* isUserCancellation= */ true);
break;
case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
- Log.i("reemademo", "RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS");
mStatus = UiStatus.TERMINATED;
mCallbacks.onUiCancellation(/* isUserCancellation= */ false);
@@ -102,7 +98,6 @@
mCallbacks.onUiSelectorInvocationFailure();
break;
default:
- Log.i("reemademo", "Unknown error code returned from the UI");
mStatus = UiStatus.IN_PROGRESS;
mCallbacks.onUiSelectorInvocationFailure();
break;
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index e820d8a..aee4f58 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -133,6 +133,7 @@
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
+ mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.updateMetricsOnResponseReceived(mProviders, componentName,
isPrimaryProviderViaProviderInfo(componentName));
if (response != null) {
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 64a73c9..b36de0b 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -39,7 +39,6 @@
/**
* For all future metric additions, this will contain their names for local usage after importing
* from {@link com.android.internal.util.FrameworkStatsLog}.
- * TODO(b/271135048) - Emit all atoms, including all V4 atoms (specifically the rest of track 1).
*/
public class MetricUtilities {
private static final boolean LOG_FLAG = true;
@@ -101,7 +100,9 @@
*/
protected static int getMetricTimestampDifferenceMicroseconds(long t2, long t1) {
if (t2 - t1 > Integer.MAX_VALUE) {
- throw new ArithmeticException("Input timestamps are too far apart and unsupported");
+ Slog.i(TAG, "Input timestamps are too far apart and unsupported, "
+ + "falling back to default int");
+ return DEFAULT_INT_32;
}
if (t2 < t1) {
Slog.i(TAG, "The timestamps aren't in expected order, falling back to default int");
@@ -229,7 +230,7 @@
authenticationMetric.isAuthReturned()
);
} catch (Exception e) {
- Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during candidate auth metric logging: " + e);
}
}
@@ -252,22 +253,18 @@
}
var sessions = providers.values();
for (var session : sessions) {
- try {
- var metric = session.getProviderSessionMetric()
- .getCandidatePhasePerProviderMetric();
- FrameworkStatsLog.write(
- FrameworkStatsLog.CREDENTIAL_MANAGER_GET_REPORTED,
- /* session_id */ metric.getSessionIdProvider(),
- /* sequence_num */ emitSequenceId,
- /* candidate_provider_uid */ metric.getCandidateUid(),
- /* response_unique_classtypes */
- metric.getResponseCollective().getUniqueResponseStrings(),
- /* per_classtype_counts */
- metric.getResponseCollective().getUniqueResponseCounts()
- );
- } catch (Exception e) {
- Slog.w(TAG, "Unexpected exception during get metric logging" + e);
- }
+ var metric = session.getProviderSessionMetric()
+ .getCandidatePhasePerProviderMetric();
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.CREDENTIAL_MANAGER_GET_REPORTED,
+ /* session_id */ metric.getSessionIdProvider(),
+ /* sequence_num */ emitSequenceId,
+ /* candidate_provider_uid */ metric.getCandidateUid(),
+ /* response_unique_classtypes */
+ metric.getResponseCollective().getUniqueResponseStrings(),
+ /* per_classtype_counts */
+ metric.getResponseCollective().getUniqueResponseCounts()
+ );
}
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during candidate get metric logging: " + e);
@@ -399,7 +396,7 @@
/* caller_uid */ callingUid,
/* api_status */ apiStatus.getMetricCode());
} catch (Exception e) {
- Slog.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during simple v2 metric logging: " + e);
}
}
@@ -505,7 +502,7 @@
candidateAggregateMetric.isAuthReturned()
);
} catch (Exception e) {
- Slog.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during total candidate metric logging: " + e);
}
}
@@ -570,7 +567,7 @@
/* primary_indicated */ finalPhaseMetric.isPrimary()
);
} catch (Exception e) {
- Slog.w(TAG, "Unexpected error during metric logging: " + e);
+ Slog.w(TAG, "Unexpected error during final no uid metric logging: " + e);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 25f20ca..6f79852 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -54,7 +54,7 @@
private static final String TAG = "ProviderCreateSession";
// Key to be used as an entry key for a save entry
- private static final String SAVE_ENTRY_KEY = "save_entry_key";
+ public static final String SAVE_ENTRY_KEY = "save_entry_key";
// Key to be used as an entry key for a remote entry
private static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@@ -193,11 +193,13 @@
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
- mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false,
+ ((RequestSession) mCallbacks).mRequestSessionMetric.getInitialPhaseMetric());
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false,
+ ((RequestSession) mCallbacks).mRequestSessionMetric.getInitialPhaseMetric());
updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 14260f0..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;
}
@@ -435,7 +440,7 @@
BeginGetCredentialResponse response = PendingIntentResultHandler
.extractResponseContent(providerPendingIntentResponse
.getResultData());
- mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true, null);
if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) {
addToInitialRemoteResponse(response, /*isInitialResponse=*/ false);
// Additional content received is in the form of new response content.
@@ -473,12 +478,14 @@
addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
// Log the data.
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
- mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false,
+ null);
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
return;
}
- mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
+ mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false,
+ null);
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 83f21af..f2055d0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -215,8 +215,10 @@
CredentialsSource source) {
setStatus(status);
boolean isPrimary = mProviderInfo != null && mProviderInfo.isPrimary();
- mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status),
- isCompletionStatus(status), mProviderSessionUid,
+ mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status)
+ || isStatusWaitingForRemoteResponse(status),
+ isCompletionStatus(status) || isUiInvokingStatus(status),
+ mProviderSessionUid,
/*isAuthEntry*/source == CredentialsSource.AUTH_ENTRY,
/*isPrimary*/isPrimary);
mCallbacks.onProviderStatusChanged(status, mComponentName, source);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java
index 62b8f24..1ac8a71 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java
@@ -43,9 +43,6 @@
// Indicates if this provider returned from the authentication entry query, default false
private boolean mAuthReturned = false;
- // TODO(b/271135048) - Match the atom and provide a clean per provider session metric
- // encapsulation.
-
public BrowsedAuthenticationMetric(int sessionIdProvider) {
mSessionIdProvider = sessionIdProvider;
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java
index 339c221..9154778 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java
@@ -16,6 +16,7 @@
package com.android.server.credentials.metrics;
+import com.android.server.credentials.MetricUtilities;
import com.android.server.credentials.ProviderSession;
import com.android.server.credentials.metrics.shared.ResponseCollective;
@@ -29,7 +30,7 @@
*/
public class CandidateAggregateMetric {
- private static final String TAG = "CandidateProviderMetric";
+ private static final String TAG = "CandidateTotalMetric";
// The session id of this provider metric
private final int mSessionIdProvider;
// Indicates if this provider returned from the candidate query phase,
@@ -74,8 +75,6 @@
/**
* This will take all the candidate data captured and aggregate that information.
- * TODO(b/271135048) : Add on authentication entry outputs from track 2 here as well once
- * generated
* @param providers the providers associated with the candidate flow
*/
public void collectAverages(Map<String, ProviderSession> providers) {
@@ -88,11 +87,15 @@
Map<String, Integer> responseCountQuery = new LinkedHashMap<>();
Map<EntryEnum, Integer> entryCountQuery = new LinkedHashMap<>();
var providerSessions = providers.values();
- long min_query_start = Integer.MAX_VALUE;
- long max_query_end = Integer.MIN_VALUE;
+ long min_query_start = Long.MAX_VALUE;
+ long max_query_end = Long.MIN_VALUE;
for (var session : providerSessions) {
var sessionMetric = session.getProviderSessionMetric();
var candidateMetric = sessionMetric.getCandidatePhasePerProviderMetric();
+ if (candidateMetric.getCandidateUid() == MetricUtilities.DEFAULT_INT_32) {
+ mNumProviders--;
+ continue; // Do not aggregate this one and reduce the size of actual candidates
+ }
if (mServiceBeganTimeNanoseconds == -1) {
mServiceBeganTimeNanoseconds = candidateMetric.getServiceBeganTimeNanoseconds();
}
@@ -119,15 +122,17 @@
}
private void collectAuthAggregates(Map<String, ProviderSession> providers) {
- mNumProviders = providers.size();
Map<String, Integer> responseCountAuth = new LinkedHashMap<>();
Map<EntryEnum, Integer> entryCountAuth = new LinkedHashMap<>();
var providerSessions = providers.values();
for (var session : providerSessions) {
var sessionMetric = session.getProviderSessionMetric();
var authMetrics = sessionMetric.getBrowsedAuthenticationMetric();
- mNumAuthEntriesTapped += authMetrics.size();
for (var authMetric : authMetrics) {
+ if (authMetric.getProviderUid() == MetricUtilities.DEFAULT_INT_32) {
+ continue; // skip this unfilled base auth entry
+ }
+ mNumAuthEntriesTapped++;
mAuthReturned = mAuthReturned || authMetric.isAuthReturned();
ResponseCollective authCollective = authMetric.getAuthEntryCollective();
ResponseCollective.combineTypeCountMaps(responseCountAuth,
diff --git a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
index 530f01c..226cd2c 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/EntryEnum.java
@@ -28,6 +28,8 @@
import android.util.Slog;
+import com.android.server.credentials.ProviderCreateSession;
+
import java.util.AbstractMap;
import java.util.Map;
@@ -52,6 +54,8 @@
new AbstractMap.SimpleEntry<>(REMOTE_ENTRY_KEY,
REMOTE_ENTRY.mInnerMetricCode),
new AbstractMap.SimpleEntry<>(CREDENTIAL_ENTRY_KEY,
+ CREDENTIAL_ENTRY.mInnerMetricCode),
+ new AbstractMap.SimpleEntry<>(ProviderCreateSession.SAVE_ENTRY_KEY,
CREDENTIAL_ENTRY.mInnerMetricCode)
);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
index 44d845e..c1f6b47 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -20,6 +20,7 @@
import static com.android.server.credentials.MetricUtilities.generateMetricKey;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.BeginGetCredentialResponse;
import android.service.credentials.CredentialEntry;
@@ -205,18 +206,22 @@
*
* @param response contains entries and data from the candidate provider responses
* @param isAuthEntry indicates if this is an auth entry collection or not
+ * @param initialPhaseMetric for create flows, this helps identify the response type, which
+ * will identify the *type* of create flow, especially important in
+ * track 2. This is expected to be null in get flows.
* @param <R> the response type associated with the API flow in progress
*/
- public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry) {
+ public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry,
+ @Nullable InitialPhaseMetric initialPhaseMetric) {
try {
if (response instanceof BeginGetCredentialResponse) {
beginGetCredentialResponseCollectionCandidateEntryMetrics(
(BeginGetCredentialResponse) response, isAuthEntry);
} else if (response instanceof BeginCreateCredentialResponse) {
beginCreateCredentialResponseCollectionCandidateEntryMetrics(
- (BeginCreateCredentialResponse) response);
+ (BeginCreateCredentialResponse) response, initialPhaseMetric);
} else {
- Slog.i(TAG, "Your response type is unsupported for metric logging");
+ Slog.i(TAG, "Your response type is unsupported for candidate metric logging");
}
} catch (Exception e) {
Slog.i(TAG, "Unexpected error during candidate entry metric logging: " + e);
@@ -245,7 +250,6 @@
String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT);
responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
});
-
ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
}
@@ -262,19 +266,21 @@
}
private void beginCreateCredentialResponseCollectionCandidateEntryMetrics(
- BeginCreateCredentialResponse response) {
+ BeginCreateCredentialResponse response, InitialPhaseMetric initialPhaseMetric) {
Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
var createEntries = response.getCreateEntries();
- int numRemoteEntry = response.getRemoteCreateEntry() != null ? MetricUtilities.ZERO :
+ int numRemoteEntry = response.getRemoteCreateEntry() == null ? MetricUtilities.ZERO :
MetricUtilities.UNIT;
int numCreateEntries = createEntries.size();
entryCounts.put(EntryEnum.REMOTE_ENTRY, numRemoteEntry);
entryCounts.put(EntryEnum.CREDENTIAL_ENTRY, numCreateEntries);
Map<String, Integer> responseCounts = new LinkedHashMap<>();
- responseCounts.put(MetricUtilities.DEFAULT_STRING, numCreateEntries);
- // We don't store create response because it's directly related to the request
- // We do still store the count, however
+ String[] requestStrings = initialPhaseMetric == null ? new String[0] :
+ initialPhaseMetric.getUniqueRequestStrings();
+ if (requestStrings.length > 0) {
+ responseCounts.put(requestStrings[0], initialPhaseMetric.getUniqueRequestCounts()[0]);
+ }
ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 281f3cc..83b57c4 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -26,13 +26,17 @@
import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase;
import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase;
import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal;
+import static com.android.server.credentials.metrics.ApiName.GET_CREDENTIAL;
+import static com.android.server.credentials.metrics.ApiName.GET_CREDENTIAL_VIA_REGISTRY;
import android.annotation.NonNull;
import android.content.ComponentName;
+import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.ui.UserSelectionDialogResult;
import android.util.Slog;
+import com.android.server.credentials.MetricUtilities;
import com.android.server.credentials.ProviderSession;
import java.util.ArrayList;
@@ -112,7 +116,7 @@
mInitialPhaseMetric.setCallerUid(mCallingUid);
mInitialPhaseMetric.setApiName(metricCode);
} catch (Exception e) {
- Slog.i(TAG, "Unexpected error collecting initial metrics: " + e);
+ Slog.i(TAG, "Unexpected error collecting initial phase metric start info: " + e);
}
}
@@ -177,9 +181,12 @@
*
* @param origin indicates if an origin was passed in or not
*/
- public void collectCreateFlowInitialMetricInfo(boolean origin) {
+ public void collectCreateFlowInitialMetricInfo(boolean origin,
+ CreateCredentialRequest request) {
try {
mInitialPhaseMetric.setOriginSpecified(origin);
+ mInitialPhaseMetric.setRequestCounts(Map.of(generateMetricKey(request.getType(),
+ DELTA_RESPONSES_CUT), MetricUtilities.UNIT));
} catch (Exception e) {
Slog.i(TAG, "Unexpected error collecting create flow metric: " + e);
}
@@ -195,7 +202,7 @@
0) + 1);
});
} catch (Exception e) {
- Slog.i(TAG, "Unexpected error during get request metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during get request count map metric logging: " + e);
}
return uniqueRequestCounts;
}
@@ -210,7 +217,7 @@
mInitialPhaseMetric.setOriginSpecified(request.getOrigin() != null);
mInitialPhaseMetric.setRequestCounts(getRequestCountMap(request));
} catch (Exception e) {
- Slog.i(TAG, "Unexpected error collecting get flow metric: " + e);
+ Slog.i(TAG, "Unexpected error collecting get flow initial metric: " + e);
}
}
@@ -277,7 +284,7 @@
mChosenProviderFinalPhaseMetric.setChosenProviderStatus(
finalStatus.getMetricCode());
} catch (Exception e) {
- Slog.i(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during final phase provider status metric logging: " + e);
}
}
@@ -367,7 +374,11 @@
public void logCandidatePhaseMetrics(Map<String, ProviderSession> providers) {
try {
logApiCalledCandidatePhase(providers, ++mSequenceCounter, mInitialPhaseMetric);
- logApiCalledCandidateGetMetric(providers, mSequenceCounter);
+ if (mInitialPhaseMetric.getApiName() == GET_CREDENTIAL.getMetricCode()
+ || mInitialPhaseMetric.getApiName() == GET_CREDENTIAL_VIA_REGISTRY
+ .getMetricCode()) {
+ logApiCalledCandidateGetMetric(providers, mSequenceCounter);
+ }
} catch (Exception e) {
Slog.i(TAG, "Unexpected error during candidate metric emit: " + e);
}
@@ -405,7 +416,7 @@
}
logApiCalledAuthenticationMetric(browsedAuthenticationMetric, ++mSequenceCounter);
} catch (Exception e) {
- Slog.i(TAG, "Unexpected error during metric logging: " + e);
+ Slog.i(TAG, "Unexpected error during auth entry metric emit: " + e);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
index 0958a84..ceb9571 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/shared/ResponseCollective.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
+import com.android.server.credentials.MetricUtilities;
import com.android.server.credentials.metrics.EntryEnum;
import java.util.Collections;
@@ -121,7 +122,7 @@
* @return a count of this particular entry enum stored by this provider
*/
public int getCountForEntry(EntryEnum e) {
- return mEntryCounts.get(e);
+ return mEntryCounts.getOrDefault(e, MetricUtilities.ZERO);
}
/**
@@ -167,7 +168,7 @@
public static <T> Map<T, Integer> combineTypeCountMaps(Map<T, Integer> first,
Map<T, Integer> second) {
for (T response : second.keySet()) {
- first.merge(response, first.getOrDefault(response, 0), Integer::sum);
+ first.put(response, first.getOrDefault(response, 0) + second.get(response));
}
return first;
}
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 9d1c77d..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()
@@ -15135,7 +15145,7 @@
return;
}
- if ((flags == 0) || ((flags & ~(allowedFlags)) != 0)) {
+ if ((flags & ~(allowedFlags)) != 0) {
throw new SecurityException(
"Permitted lock task features when managing a financed device: "
+ "LOCK_TASK_FEATURE_SYSTEM_INFO, LOCK_TASK_FEATURE_KEYGUARD, "
@@ -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/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index d9467a5..c7a71ee 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -39,7 +39,7 @@
"PackageManagerServiceHostTestsIntentVerifyUtils",
"block_device_writer_jar",
],
- test_suites: ["general-tests"],
+ test_suites: ["device-tests"],
data: [
":PackageManagerTestApex",
":PackageManagerTestApexApp",
diff --git a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
index 2382548..f594f6f 100644
--- a/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/host/AndroidTest.xml
@@ -30,6 +30,7 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
+ <option name="install-arg" value="-g" />
<option name="test-file-name" value="PackageManagerServiceServerTests.apk" />
</target_preparer>
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/assets/AppOpsUpgradeTest/appops-version-3.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-3.xml
new file mode 100644
index 0000000..1363bf7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-3.xml
@@ -0,0 +1,801 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<!--
+ ~ 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.
+ -->
+
+<app-ops v="3">
+ <uid n="1001">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="15" m="0" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="1002">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="15" m="0" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10077">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10079">
+ <op n="116" m="1" />
+ </uid>
+ <uid n="10080">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10081">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10086">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10087">
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10090">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ </uid>
+ <uid n="10096">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10112">
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10113">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10114">
+ <op n="4" m="1" />
+ </uid>
+ <uid n="10115">
+ <op n="0" m="1" />
+ </uid>
+ <uid n="10116">
+ <op n="0" m="1" />
+ </uid>
+ <uid n="10117">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="13" m="1" />
+ <op n="14" m="1" />
+ <op n="16" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10118">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10119">
+ <op n="11" m="1" />
+ <op n="77" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10120">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="6" m="1" />
+ <op n="7" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="54" m="1" />
+ <op n="59" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10121">
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10122">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10123">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10124">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ </uid>
+ <uid n="10125">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10127">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="65" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10129">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10130">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10131">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10132">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="69" m="1" />
+ <op n="79" m="1" />
+ </uid>
+ <uid n="10133">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10136">
+ <op n="0" m="1" />
+ <op n="4" m="1" />
+ <op n="77" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ <op n="114" m="1" />
+ </uid>
+ <uid n="10137">
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10138">
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10140">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10141">
+ <op n="11" m="1" />
+ <op n="27" m="1" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10142">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10144">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="27" m="4" />
+ <op n="111" m="1" />
+ </uid>
+ <uid n="10145">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10149">
+ <op n="11" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10150">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10151">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10152">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10154">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10155">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ </uid>
+ <uid n="10157">
+ <op n="13" m="1" />
+ </uid>
+ <uid n="10158">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10160">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10161">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="77" m="1" />
+ <op n="87" m="1" />
+ <op n="111" m="1" />
+ <op n="114" m="1" />
+ </uid>
+ <uid n="10162">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="15" m="0" />
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ <op n="89" m="0" />
+ </uid>
+ <uid n="10163">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="56" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10164">
+ <op n="26" m="1" />
+ <op n="27" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="69" m="1" />
+ <op n="79" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10169">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10170">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10171">
+ <op n="26" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10172">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10173">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="23" m="0" />
+ <op n="26" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ <op n="65" m="1" />
+ </uid>
+ <uid n="10175">
+ <op n="0" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ </uid>
+ <uid n="10178">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="27" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10179">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10180">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10181">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="1110181">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="107" m="2" />
+ </uid>
+ <uid n="10182">
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10183">
+ <op n="11" m="1" />
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10184">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="4" />
+ </uid>
+ <uid n="10185">
+ <op n="8" m="1" />
+ <op n="59" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10187">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10189">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10190">
+ <op n="0" m="1" />
+ <op n="13" m="1" />
+ </uid>
+ <uid n="10191">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10192">
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10193">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10197">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10198">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="77" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="90" m="1" />
+ <op n="107" m="0" />
+ <op n="111" m="1" />
+ <op n="133" m="0" />
+ </uid>
+ <uid n="10199">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10200">
+ <op n="11" m="1" />
+ <op n="65" m="1" />
+ <op n="107" m="1" />
+ <op n="133" m="1" />
+ </uid>
+ <uid n="1110200">
+ <op n="11" m="1" />
+ <op n="65" m="1" />
+ <op n="107" m="2" />
+ <op n="133" m="2"/>
+ </uid>
+ <uid n="10201">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="62" m="1" />
+ <op n="84" m="0" />
+ <op n="86" m="0" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10206">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ <op n="26" m="4" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10209">
+ <op n="11" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10210">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10212">
+ <op n="11" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10214">
+ <op n="26" m="4" />
+ </uid>
+ <uid n="10216">
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10225">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10229">
+ <op n="11" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10231">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10232">
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10234">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="11" m="1" />
+ <op n="13" m="1" />
+ <op n="20" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10235">
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10237">
+ <op n="0" m="4" />
+ <op n="1" m="4" />
+ </uid>
+ <uid n="10238">
+ <op n="26" m="4" />
+ <op n="27" m="4" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10240">
+ <op n="112" m="1" />
+ </uid>
+ <uid n="10241">
+ <op n="59" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10245">
+ <op n="13" m="1" />
+ <op n="51" m="1" />
+ </uid>
+ <uid n="10247">
+ <op n="0" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="0" />
+ <op n="90" m="1" />
+ </uid>
+ <uid n="10254">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10255">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10256">
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10258">
+ <op n="11" m="1" />
+ </uid>
+ <uid n="10260">
+ <op n="51" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <uid n="10262">
+ <op n="15" m="0" />
+ </uid>
+ <uid n="10266">
+ <op n="0" m="4" />
+ </uid>
+ <uid n="10267">
+ <op n="0" m="1" />
+ <op n="1" m="1" />
+ <op n="4" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="62" m="1" />
+ <op n="77" m="1" />
+ <op n="81" m="1" />
+ <op n="83" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ <op n="107" m="2" />
+ <op n="111" m="1" />
+ <op n="133" m="2" />
+ </uid>
+ <uid n="10268">
+ <op n="4" m="1" />
+ <op n="11" m="1" />
+ <op n="62" m="1" />
+ </uid>
+ <uid n="10269">
+ <op n="11" m="1" />
+ <op n="26" m="1" />
+ <op n="27" m="1" />
+ <op n="59" m="1" />
+ <op n="60" m="1" />
+ <op n="85" m="1" />
+ <op n="87" m="1" />
+ </uid>
+ <pkg n="com.google.android.iwlan">
+ <uid n="0">
+ <op n="1" />
+ <op n="75" m="0" />
+ </uid>
+ </pkg>
+ <pkg n="com.android.phone">
+ <uid n="0">
+ <op n="1" />
+ <op n="75" m="0" />
+ </uid>
+ </pkg>
+ <pkg n="android">
+ <uid n="1000">
+ <op n="0">
+ <st n="214748364801" t="1670287941040" />
+ </op>
+ <op n="4">
+ <st n="214748364801" t="1670289665522" />
+ </op>
+ <op n="6">
+ <st n="214748364801" t="1670287946650" />
+ </op>
+ <op n="8">
+ <st n="214748364801" t="1670289624396" />
+ </op>
+ <op n="14">
+ <st n="214748364801" t="1670287951031" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670291786337" d="156" />
+ </op>
+ <op n="41">
+ <st id="SensorNotificationService" n="214748364801" t="1670287585567" d="4251183" />
+ <st id="CountryDetector" n="214748364801" t="1670287583306" d="6700" />
+ </op>
+ <op n="43">
+ <st n="214748364801" r="1670291755062" />
+ </op>
+ <op n="61">
+ <st n="214748364801" r="1670291754997" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291473903" />
+ <st id="GnssService" n="214748364801" r="1670288044920" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670291441554" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.server.telecom">
+ <uid n="1000">
+ <op n="6">
+ <st n="214748364801" t="1670287609092" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670287583728" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.settings">
+ <uid n="1000">
+ <op n="43">
+ <st n="214748364801" r="1670291447349" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291399231" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670291756910" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.phone">
+ <uid n="1001">
+ <op n="15">
+ <st n="214748364801" t="1670287951022" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670291786177" />
+ </op>
+ <op n="105">
+ <st n="214748364801" r="1670291756403" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.bluetooth">
+ <uid n="1002">
+ <op n="4">
+ <st n="214748364801" t="1670289671076" />
+ </op>
+ <op n="40">
+ <st n="214748364801" t="1670287585676" d="8" />
+ </op>
+ <op n="43">
+ <st n="214748364801" r="1670287585818" />
+ </op>
+ <op n="77">
+ <st n="214748364801" t="1670288037629" />
+ </op>
+ <op n="111">
+ <st n="214748364801" t="1670287592081" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.vending">
+ <uid n="10136">
+ <op n="40">
+ <st n="429496729601" t="1670289621210" d="114" />
+ <st n="858993459201" t="1670289879730" d="349" />
+ <st n="1288490188801" t="1670287942622" d="937" />
+ </op>
+ <op n="43">
+ <st n="429496729601" r="1670289755305" />
+ <st n="858993459201" r="1670288019246" />
+ <st n="1073741824001" r="1670289571783" />
+ <st n="1288490188801" r="1670289373336" />
+ </op>
+ <op n="76">
+ <st n="429496729601" t="1670289748735" d="15991" />
+ <st n="858993459201" t="1670291395180" d="79201" />
+ <st n="1073741824001" t="1670291395168" d="12" />
+ <st n="1288490188801" t="1670291526029" d="3" />
+ <st n="1503238553601" t="1670291526032" d="310718" />
+ </op>
+ <op n="105">
+ <st n="429496729601" r="1670289538910" />
+ <st n="858993459201" r="1670288054519" />
+ <st n="1073741824001" r="1670287599379" />
+ <st n="1288490188801" r="1670289526854" />
+ <st n="1503238553601" r="1670289528242" />
+ </op>
+ </uid>
+ </pkg>
+ <pkg n="com.android.nfc">
+ <uid n="1027">
+ <op n="40">
+ <st n="214748364801" t="1670291786330" d="22" />
+ </op>
+ </uid>
+ </pkg>
+</app-ops>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index f82d246..a614c4d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -37,6 +37,7 @@
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
@@ -60,6 +61,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
@@ -82,13 +84,16 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.StickyBroadcast;
import com.android.server.am.ProcessList.IsolatedUidRange;
@@ -106,6 +111,7 @@
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.io.File;
import java.util.ArrayList;
@@ -137,7 +143,9 @@
private static final String TEST_EXTRA_KEY1 = "com.android.server.am.TEST_EXTRA_KEY1";
private static final String TEST_EXTRA_VALUE1 = "com.android.server.am.TEST_EXTRA_VALUE1";
-
+ private static final String PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS =
+ "apply_sdk_sandbox_next_restrictions";
+ private static final String APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS = ":isSdkSandboxNext";
private static final int TEST_UID = 11111;
private static final int USER_ID = 666;
@@ -154,6 +162,7 @@
};
private static PackageManagerInternal sPackageManagerInternal;
+ private static ProcessList.ProcessListSettingsListener sProcessListSettingsListener;
@BeforeClass
public static void setUpOnce() {
@@ -181,7 +190,6 @@
private ActivityManagerService mAms;
private HandlerThread mHandlerThread;
private TestHandler mHandler;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -203,6 +211,15 @@
mAms.mActivityTaskManager.initialize(null, null, mHandler.getLooper());
mHandler.setRunnablesToIgnore(
List.of(mAms.mUidObserverController.getDispatchRunnableForTest()));
+
+ // Required for updating DeviceConfig.
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .adoptShellPermissionIdentity(
+ Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.WRITE_DEVICE_CONFIG);
+ sProcessListSettingsListener = mAms.mProcessList.getProcessListSettingsListener();
+ assertThat(sProcessListSettingsListener).isNotNull();
}
private void mockNoteOperation() {
@@ -216,6 +233,12 @@
@After
public void tearDown() {
mHandlerThread.quit();
+ InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation()
+ .dropShellPermissionIdentity();
+ if (sProcessListSettingsListener != null) {
+ sProcessListSettingsListener.unregisterObserver();
+ }
}
@SuppressWarnings("GuardedBy")
@@ -268,6 +291,77 @@
false); // expectNotify
}
+ @SuppressWarnings("GuardedBy")
+ @SmallTest
+ @Test
+ public void defaultSdkSandboxNextRestrictions() throws Exception {
+ sProcessListSettingsListener.onPropertiesChanged(
+ new DeviceConfig.Properties(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ Map.of(PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, "")));
+ assertThat(
+ sProcessListSettingsListener.applySdkSandboxRestrictionsNext())
+ .isFalse();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @SmallTest
+ @Test
+ public void doNotApplySdkSandboxNextRestrictions() throws Exception {
+ MockitoSession mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process.class).startMocking();
+ try {
+ sProcessListSettingsListener.onPropertiesChanged(
+ new DeviceConfig.Properties(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ Map.of(PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, "false")));
+ assertThat(
+ sProcessListSettingsListener.applySdkSandboxRestrictionsNext())
+ .isFalse();
+ ExtendedMockito.doReturn(true).when(() -> Process.isSdkSandboxUid(anyInt()));
+ ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "com.android.sdksandbox";
+ info.seInfo = "default:targetSdkVersion=34:complete";
+ final ProcessRecord appRec = new ProcessRecord(
+ mAms, info, TAG, Process.FIRST_SDK_SANDBOX_UID,
+ /* sdkSandboxClientPackageName= */ "com.example.client",
+ /* definingUid= */ 0, /* definingProcessName= */ "");
+ assertThat(mAms.mProcessList.updateSeInfo(appRec)).doesNotContain(
+ APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS);
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+ @SuppressWarnings("GuardedBy")
+ @SmallTest
+ @Test
+ public void applySdkSandboxNextRestrictions() throws Exception {
+ MockitoSession mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process.class).startMocking();
+ try {
+ sProcessListSettingsListener.onPropertiesChanged(
+ new DeviceConfig.Properties(
+ DeviceConfig.NAMESPACE_ADSERVICES,
+ Map.of(PROPERTY_APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS, "true")));
+ assertThat(
+ sProcessListSettingsListener.applySdkSandboxRestrictionsNext())
+ .isTrue();
+ ExtendedMockito.doReturn(true).when(() -> Process.isSdkSandboxUid(anyInt()));
+ ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "com.android.sdksandbox";
+ info.seInfo = "default:targetSdkVersion=34:complete";
+ final ProcessRecord appRec = new ProcessRecord(
+ mAms, info, TAG, Process.FIRST_SDK_SANDBOX_UID,
+ /* sdkSandboxClientPackageName= */ "com.example.client",
+ /* definingUid= */ 0, /* definingProcessName= */ "");
+ assertThat(mAms.mProcessList.updateSeInfo(appRec)).contains(
+ APPLY_SDK_SANDBOX_NEXT_RESTRICTIONS);
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+
+
private UidRecord addUidRecord(int uid) {
final UidRecord uidRec = new UidRecord(uid, mAms);
uidRec.procStateSeqWaitingForNetwork = 1;
@@ -648,24 +742,24 @@
broadcastIntent(intent1, null, true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false));
+ StickyBroadcast.create(intent1, false, Process.myUid()));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));
broadcastIntent(intent2, options.toBundle(), true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false));
+ StickyBroadcast.create(intent1, false, Process.myUid()));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
- StickyBroadcast.create(intent2, true));
+ StickyBroadcast.create(intent2, true, Process.myUid()));
assertNull(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER));
broadcastIntent(intent3, null, true);
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION1, TEST_USER),
- StickyBroadcast.create(intent1, false));
+ StickyBroadcast.create(intent1, false, Process.myUid()));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION2, TEST_USER),
- StickyBroadcast.create(intent2, true));
+ StickyBroadcast.create(intent2, true, Process.myUid()));
assertStickyBroadcasts(mAms.getStickyBroadcasts(TEST_ACTION3, TEST_USER),
- StickyBroadcast.create(intent3, false));
+ StickyBroadcast.create(intent3, false, Process.myUid()));
}
@SuppressWarnings("GuardedBy")
@@ -698,6 +792,9 @@
if (a.deferUntilActive != b.deferUntilActive) {
return false;
}
+ if (a.originalCallingUid != b.originalCallingUid) {
+ return false;
+ }
return true;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 581fe4a..f47954b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -537,6 +537,30 @@
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
}
+ @Test
+ public void testRunnableAt_persistentProc() {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver()));
+ enqueueOrReplaceBroadcast(queue, timeTickRecord, 0);
+
+ assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+
+ doReturn(true).when(mProcess).isPersistent();
+ queue.setProcessAndUidState(mProcess, false, false);
+ assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_PERSISTENT, queue.getRunnableAtReason());
+
+ doReturn(false).when(mProcess).isPersistent();
+ queue.setProcessAndUidState(mProcess, false, false);
+ assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ }
+
/**
* Verify that a cached process that would normally be delayed becomes
* immediately runnable when the given broadcast is enqueued.
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 5474c20..92d1118 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -17,6 +17,7 @@
package com.android.server.appop;
import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT;
import static android.app.AppOpsManager._NUM_OP;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
@@ -31,6 +32,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.never;
@@ -44,6 +46,7 @@
import android.content.res.AssetManager;
import android.os.Handler;
import android.os.UserHandle;
+import android.permission.PermissionManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
@@ -87,6 +90,10 @@
"AppOpsUpgradeTest/appops-unversioned.xml";
private static final String APP_OPS_VERSION_1_ASSET_PATH =
"AppOpsUpgradeTest/appops-version-1.xml";
+
+ private static final String APP_OPS_VERSION_3_ASSET_PATH =
+ "AppOpsUpgradeTest/appops-version-3.xml";
+
private static final String APP_OPS_FILENAME = "appops-test.xml";
private static final Context sContext = InstrumentationRegistry.getTargetContext();
@@ -105,6 +112,8 @@
private PermissionManagerServiceInternal mPermissionManagerInternal;
@Mock
private Handler mHandler;
+ @Mock
+ private PermissionManager mPermissionManager;
private Object mLock = new Object();
private SparseArray<int[]> mSwitchedOps;
@@ -211,7 +220,7 @@
}
}
- private static int getModeInFile(int uid) {
+ private static int getModeInFile(int uid, int op) {
switch (uid) {
case 10198:
return 0;
@@ -222,7 +231,7 @@
case 1110181:
return 2;
default:
- return AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM);
+ return AppOpsManager.opToDefaultMode(op);
}
}
@@ -258,7 +267,7 @@
for (int userId : userIds) {
for (int appId : appIds) {
final int uid = UserHandle.getUid(userId, appId);
- final int previousMode = getModeInFile(uid);
+ final int previousMode = getModeInFile(uid, OP_SCHEDULE_EXACT_ALARM);
final int expectedMode;
if (previousMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
@@ -281,6 +290,55 @@
}
@Test
+ public void resetUseFullScreenIntent() {
+ extractAppOpsFile(APP_OPS_VERSION_3_ASSET_PATH);
+
+ String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"};
+ int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213};
+ int[] userIds = {0, 10, 11};
+ int flag = 0;
+
+ doReturn(userIds).when(mUserManagerInternal).getUserIds();
+
+ doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages(
+ AppOpsManager.opToPermission(OP_USE_FULL_SCREEN_INTENT));
+
+ doReturn(mPermissionManager).when(mTestContext).getSystemService(PermissionManager.class);
+
+ doReturn(flag).when(mPackageManager).getPermissionFlags(
+ anyString(), anyString(), isA(UserHandle.class));
+
+ doAnswer(invocation -> {
+ String pkg = invocation.getArgument(0);
+ int index = ArrayUtils.indexOf(packageNames, pkg);
+ if (index < 0) {
+ return index;
+ }
+ int userId = invocation.getArgument(2);
+ return UserHandle.getUid(userId, appIds[index]);
+ }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+
+ AppOpsCheckingServiceImpl testService = new AppOpsCheckingServiceImpl(sAppOpsFile, mLock,
+ mHandler, mTestContext, mSwitchedOps);
+ testService.readState();
+
+ synchronized (testService) {
+ testService.resetUseFullScreenIntentLocked();
+ }
+
+ for (int userId : userIds) {
+ for (int appId : appIds) {
+ final int uid = UserHandle.getUid(userId, appId);
+ final int expectedMode = AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT);
+ synchronized (testService) {
+ int mode = testService.getUidMode(uid, OP_USE_FULL_SCREEN_INTENT);
+ assertEquals(expectedMode, mode);
+ }
+ }
+ }
+ }
+
+ @Test
public void upgradeFromNoFile() {
assertFalse(sAppOpsFile.exists());
@@ -290,12 +348,14 @@
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+ doNothing().when(testService).resetUseFullScreenIntentLocked();
// trigger upgrade
testService.systemReady();
verify(testService, never()).upgradeRunAnyInBackgroundLocked();
verify(testService, never()).upgradeScheduleExactAlarmLocked();
+ verify(testService, never()).resetUseFullScreenIntentLocked();
testService.writeState();
@@ -319,12 +379,14 @@
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+ doNothing().when(testService).resetUseFullScreenIntentLocked();
// trigger upgrade
testService.systemReady();
verify(testService).upgradeRunAnyInBackgroundLocked();
verify(testService).upgradeScheduleExactAlarmLocked();
+ verify(testService).resetUseFullScreenIntentLocked();
testService.writeState();
assertTrue(parser.parse());
@@ -344,12 +406,40 @@
doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+ doNothing().when(testService).resetUseFullScreenIntentLocked();
// trigger upgrade
testService.systemReady();
verify(testService, never()).upgradeRunAnyInBackgroundLocked();
verify(testService).upgradeScheduleExactAlarmLocked();
+ verify(testService).resetUseFullScreenIntentLocked();
+
+ testService.writeState();
+ assertTrue(parser.parse());
+ assertEquals(AppOpsCheckingServiceImpl.CURRENT_VERSION, parser.mVersion);
+ }
+
+ @Test
+ public void resetFromVersion3() {
+ extractAppOpsFile(APP_OPS_VERSION_3_ASSET_PATH);
+ AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
+ assertTrue(parser.parse());
+ assertEquals(3, parser.mVersion);
+
+ AppOpsCheckingServiceImpl testService = spy(new AppOpsCheckingServiceImpl(sAppOpsFile,
+ mLock, mHandler, mTestContext, mSwitchedOps));
+ testService.readState();
+
+ doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+ doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+ doNothing().when(testService).resetUseFullScreenIntentLocked();
+
+ testService.systemReady();
+
+ verify(testService, never()).upgradeRunAnyInBackgroundLocked();
+ verify(testService, never()).upgradeScheduleExactAlarmLocked();
+ verify(testService).resetUseFullScreenIntentLocked();
testService.writeState();
assertTrue(parser.parse());
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/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 439a6efa..f88afe7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -158,7 +158,8 @@
verify(mBiometricService, never()).registerAuthenticator(
anyInt(),
- any(),
+ anyInt(),
+ anyInt(),
any());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 26a3ae1..154aa7d4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -45,14 +45,13 @@
import android.content.Context;
import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -490,16 +489,9 @@
IBiometricAuthenticator fingerprintAuthenticator = mock(IBiometricAuthenticator.class);
when(fingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(fingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
-
- final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
- id, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, type,
- false /* resetLockoutRequiresHardwareAuthToken */);
- mFingerprintSensorProps.add(props);
-
- mSensors.add(new BiometricSensor(mContext,
+ mSensors.add(new BiometricSensor(mContext, id,
TYPE_FINGERPRINT /* modality */,
- props,
+ Authenticators.BIOMETRIC_STRONG /* strength */,
fingerprintAuthenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
@@ -512,6 +504,21 @@
}
});
+ final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
+ componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */));
+
+ mFingerprintSensorProps.add(new FingerprintSensorPropertiesInternal(id,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ type,
+ false /* resetLockoutRequiresHardwareAuthToken */));
+
when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
}
@@ -519,13 +526,9 @@
IBiometricAuthenticator authenticator) throws RemoteException {
when(authenticator.isHardwareDetected(any())).thenReturn(true);
when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- mSensors.add(new BiometricSensor(mContext,
+ mSensors.add(new BiometricSensor(mContext, id,
TYPE_FACE /* modality */,
- new FaceSensorPropertiesInternal(id,
- SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, FaceSensorProperties.TYPE_UNKNOWN,
- true /* supportsFace Detection */, true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ Authenticators.BIOMETRIC_STRONG /* strength */,
authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 46fa3ab..520e1c8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -19,7 +19,6 @@
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -70,11 +69,7 @@
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -124,7 +119,6 @@
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
- private FingerprintSensorPropertiesInternal mFingerprintProps;
private BiometricService mBiometricService;
@@ -207,11 +201,6 @@
};
when(mInjector.getConfiguration(any())).thenReturn(config);
-
- mFingerprintProps = new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
- STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */);
}
@Test
@@ -347,7 +336,8 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -419,7 +409,8 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
+ mBiometricService.mImpl.registerAuthenticator(0 /* id */,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1351,13 +1342,9 @@
for (int i = 0; i < testCases.length; i++) {
final BiometricSensor sensor =
- new BiometricSensor(mContext,
+ new BiometricSensor(mContext, 0 /* id */,
TYPE_FINGERPRINT,
- new FingerprintSensorPropertiesInternal(i /* id */,
- Utils.authenticatorStrengthToPropertyStrength(testCases[i][0]),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ testCases[i][0],
mock(IBiometricAuthenticator.class)) {
@Override
boolean confirmationAlwaysRequired(int userId) {
@@ -1385,7 +1372,8 @@
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
+ mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1396,14 +1384,15 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
+ final int testId = 0;
+
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
-
- final int testId = SENSOR_ID_FINGERPRINT;
- mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
+ mBiometricService.mImpl.registerAuthenticator(testId /* id */,
+ TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
mFingerprintAuthenticator);
// Downgrade the authenticator
@@ -1503,9 +1492,11 @@
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
- 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
mBiometricService.mImpl.registerAuthenticator(
- 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
+ 0 /* id */, 2 /* modality */, 15 /* strength */,
+ mFingerprintAuthenticator);
}
@Test(expected = IllegalArgumentException.class)
@@ -1515,7 +1506,9 @@
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
- 2 /* modality */, mFingerprintProps, null /* authenticator */);
+ 0 /* id */, 2 /* modality */,
+ Authenticators.BIOMETRIC_STRONG /* strength */,
+ null /* authenticator */);
}
@Test
@@ -1526,13 +1519,8 @@
for (String s : mInjector.getConfiguration(null)) {
SensorConfig config = new SensorConfig(s);
- mBiometricService.mImpl.registerAuthenticator(config.modality,
- new FingerprintSensorPropertiesInternal(config.id,
- Utils.authenticatorStrengthToPropertyStrength(config.strength),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */),
- mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(config.id, config.modality,
+ config.strength, mFingerprintAuthenticator);
}
}
@@ -1656,12 +1644,7 @@
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_NONE);
- mBiometricService.mImpl.registerAuthenticator(modality,
- new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
- Utils.authenticatorStrengthToPropertyStrength(strength),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality, strength,
mFingerprintAuthenticator);
}
@@ -1670,13 +1653,7 @@
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_NONE);
- mBiometricService.mImpl.registerAuthenticator(modality,
- new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
- Utils.authenticatorStrengthToPropertyStrength(strength),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FaceSensorProperties.TYPE_UNKNOWN, true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
mFaceAuthenticator);
}
}
@@ -1699,27 +1676,15 @@
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(modality,
- new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
- Utils.authenticatorStrengthToPropertyStrength(strength),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UNKNOWN,
- false /* resetLockoutRequiresHardwareAuthToken */),
- mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality,
+ strength, mFingerprintAuthenticator);
}
if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(modality,
- new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
- Utils.authenticatorStrengthToPropertyStrength(strength),
- 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
- FaceSensorProperties.TYPE_UNKNOWN,
- true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */),
- mFaceAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
+ strength, mFaceAuthenticator);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
index f7539bd..ee5ab92 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
@@ -16,9 +16,6 @@
package com.android.server.biometrics;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -30,13 +27,9 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IInvalidationCallback;
-import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.face.FaceSensorProperties;
-import android.hardware.face.FaceSensorPropertiesInternal;
-import android.hardware.fingerprint.FingerprintSensorProperties;
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -49,7 +42,6 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.List;
@Presubmit
@SmallTest
@@ -67,54 +59,26 @@
public void testCallbackReceived_whenAllStrongSensorsInvalidated() throws Exception {
final IBiometricAuthenticator authenticator1 = mock(IBiometricAuthenticator.class);
when(authenticator1.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor1 = new TestSensor(mContext,
- BiometricAuthenticator.TYPE_FINGERPRINT,
- new FingerprintSensorPropertiesInternal(0 /* id */,
- STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ final TestSensor sensor1 = new TestSensor(mContext, 0 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
authenticator1);
final IBiometricAuthenticator authenticator2 = mock(IBiometricAuthenticator.class);
when(authenticator2.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor2 = new TestSensor(mContext,
- BiometricAuthenticator.TYPE_FINGERPRINT,
- new FingerprintSensorPropertiesInternal(1 /* id */,
- STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ final TestSensor sensor2 = new TestSensor(mContext, 1 /* id */,
+ BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
authenticator2);
final IBiometricAuthenticator authenticator3 = mock(IBiometricAuthenticator.class);
when(authenticator3.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor3 = new TestSensor(mContext,
- BiometricAuthenticator.TYPE_FACE,
- new FaceSensorPropertiesInternal(2 /* id */,
- STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
- FaceSensorProperties.TYPE_RGB,
- true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ final TestSensor sensor3 = new TestSensor(mContext, 2 /* id */,
+ BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG,
authenticator3);
final IBiometricAuthenticator authenticator4 = mock(IBiometricAuthenticator.class);
when(authenticator4.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor4 = new TestSensor(mContext,
- BiometricAuthenticator.TYPE_FACE,
- new FaceSensorPropertiesInternal(3 /* id */,
- STRENGTH_WEAK,
- 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
- FaceSensorProperties.TYPE_IR,
- true /* supportsFace Detection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */),
+ final TestSensor sensor4 = new TestSensor(mContext, 3 /* id */,
+ BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK,
authenticator4);
final ArrayList<BiometricSensor> sensors = new ArrayList<>();
@@ -149,9 +113,9 @@
private static class TestSensor extends BiometricSensor {
- TestSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
+ TestSensor(@NonNull Context context, int id, int modality, int strength,
@NonNull IBiometricAuthenticator impl) {
- super(context, modality, props, impl);
+ super(context, id, modality, strength, impl);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
index d3f04df..903ed90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics.sensors.face;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
@@ -68,7 +70,9 @@
@Mock
private ServiceProvider mProvider2;
@Captor
- private ArgumentCaptor<FaceSensorPropertiesInternal> mPropsCaptor;
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
private FaceSensorPropertiesInternal mProvider1Props;
private FaceSensorPropertiesInternal mProvider2Props;
@@ -78,13 +82,13 @@
public void setup() {
mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, FaceSensorProperties.TYPE_RGB,
+ List.of(), FaceSensorProperties.TYPE_RGB,
true /* supportsFace Detection */,
true /* supportsSelfIllumination */,
false /* resetLockoutRequiresHardwareAuthToken */);
mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, FaceSensorProperties.TYPE_IR,
+ List.of(), FaceSensorProperties.TYPE_IR,
true /* supportsFace Detection */,
true /* supportsSelfIllumination */,
false /* resetLockoutRequiresHardwareAuthToken */);
@@ -103,9 +107,10 @@
assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
verify(mBiometricService, times(2)).registerAuthenticator(
- eq(TYPE_FACE), mPropsCaptor.capture(), any());
- assertThat(mPropsCaptor.getAllValues())
- .containsExactly(mProvider1Props, mProvider2Props);
+ mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
index 6e09069..13c3f64 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -17,6 +17,8 @@
package com.android.server.biometrics.sensors.fingerprint;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
@@ -68,7 +70,9 @@
@Mock
private ServiceProvider mProvider2;
@Captor
- private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
+ private ArgumentCaptor<Integer> mIdCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> mStrengthCaptor;
private FingerprintSensorPropertiesInternal mProvider1Props;
private FingerprintSensorPropertiesInternal mProvider2Props;
@@ -78,11 +82,11 @@
public void setup() {
mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UNKNOWN,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
false /* resetLockoutRequiresHardwareAuthToken */);
when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
@@ -99,9 +103,10 @@
assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
verify(mBiometricService, times(2)).registerAuthenticator(
- eq(TYPE_FINGERPRINT), mPropsCaptor.capture(), any());
- assertThat(mPropsCaptor.getAllValues())
- .containsExactly(mProvider1Props, mProvider2Props);
+ mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
+ assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
+ assertThat(mStrengthCaptor.getAllValues())
+ .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 9ae56b1..2aa62d9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -117,17 +117,15 @@
private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
2 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
+ List.of(),
TYPE_REAR,
false /* resetLockoutRequiresHardwareAuthToken */);
private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
2 /* maxEnrollmentsPerUser */,
- List.of() /* componentInfo */,
+ List.of(),
TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
- @Captor
- private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
private FingerprintService mService;
@Before
@@ -178,8 +176,7 @@
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
- assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsDefault);
+ verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
}
@Test
@@ -191,8 +188,7 @@
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
- assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
@Test
@@ -202,8 +198,7 @@
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
- assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
}
private void waitForRegistration() throws Exception {
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/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index f6cf571..30ff8ba 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
@@ -191,6 +192,19 @@
}
@Test
+ public void testUpdateLayoutLimitedRefreshRate_setsDirtyFlag() {
+ SurfaceControl.RefreshRateRange layoutLimitedRefreshRate =
+ new SurfaceControl.RefreshRateRange(0, 120);
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateLayoutLimitedRefreshRateLocked(layoutLimitedRefreshRate);
+ assertTrue(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+ }
+
+ @Test
public void testUpdateRefreshRateThermalThrottling() {
SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
@@ -207,6 +221,45 @@
}
@Test
+ public void testUpdateRefreshRateThermalThrottling_setsDirtyFlag() {
+ SparseArray<SurfaceControl.RefreshRateRange> refreshRanges = new SparseArray<>();
+ refreshRanges.put(0, new SurfaceControl.RefreshRateRange(0, 120));
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateThermalRefreshRateThrottling(refreshRanges);
+ assertTrue(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+ }
+
+ @Test
+ public void testUpdateDisplayGroupIdLocked() {
+ int newId = 999;
+ DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
+ mLogicalDisplay.updateDisplayGroupIdLocked(newId);
+ DisplayInfo info2 = mLogicalDisplay.getDisplayInfoLocked();
+ // Display info should only be updated when updateLocked is called
+ assertEquals(info2, info1);
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ DisplayInfo info3 = mLogicalDisplay.getDisplayInfoLocked();
+ assertNotEquals(info3, info2);
+ assertEquals(newId, info3.displayGroupId);
+ }
+
+ @Test
+ public void testUpdateDisplayGroupIdLocked_setsDirtyFlag() {
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateDisplayGroupIdLocked(99);
+ assertTrue(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+ }
+
+ @Test
public void testSetThermalBrightnessThrottlingDataId() {
String brightnessThrottlingDataId = "throttling_data_id";
DisplayInfo info1 = mLogicalDisplay.getDisplayInfoLocked();
@@ -220,4 +273,15 @@
assertNotEquals(info3, info2);
assertEquals(brightnessThrottlingDataId, info3.thermalBrightnessThrottlingDataId);
}
+
+ @Test
+ public void testSetThermalBrightnessThrottlingDataId_setsDirtyFlag() {
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.setThermalBrightnessThrottlingDataIdLocked("99");
+ assertTrue(mLogicalDisplay.isDirtyLocked());
+
+ mLogicalDisplay.updateLocked(mDeviceRepo);
+ assertFalse(mLogicalDisplay.isDirtyLocked());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
index 20e2692..bfd4072 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserJourneyLoggerTest.java
@@ -27,6 +27,7 @@
import static com.android.server.pm.UserJourneyLogger.EVENT_STATE_NONE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_GRANT_ADMIN;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_CREATE;
+import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_LIFECYCLE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_REMOVE;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_START;
import static com.android.server.pm.UserJourneyLogger.USER_JOURNEY_USER_STOP;
@@ -499,6 +500,27 @@
0x00000402, ERROR_CODE_UNSPECIFIED, 3);
}
+ @Test
+ public void testUserLifecycleJourney() {
+ final long startTime = System.currentTimeMillis();
+ final UserJourneyLogger.UserJourneySession session = mUserJourneyLogger
+ .startSessionForDelayedJourney(10, USER_JOURNEY_USER_LIFECYCLE, startTime);
+
+
+ final UserLifecycleJourneyReportedCaptor report = new UserLifecycleJourneyReportedCaptor();
+ final UserInfo targetUser = new UserInfo(10, "test target user",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_FULL);
+ mUserJourneyLogger.logDelayedUserJourneyFinishWithError(0, targetUser,
+ USER_JOURNEY_USER_LIFECYCLE, ERROR_CODE_UNSPECIFIED);
+
+
+ report.captureAndAssert(mUserJourneyLogger, session.mSessionId,
+ USER_JOURNEY_USER_LIFECYCLE, 0, 10,
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__FULL_SECONDARY,
+ 0x00000402, ERROR_CODE_UNSPECIFIED, 1);
+ assertThat(report.mElapsedTime.getValue() > 0L).isTrue();
+ }
+
static class UserLifecycleJourneyReportedCaptor {
ArgumentCaptor<Long> mSessionId = ArgumentCaptor.forClass(Long.class);
ArgumentCaptor<Integer> mJourney = ArgumentCaptor.forClass(Integer.class);
@@ -507,6 +529,7 @@
ArgumentCaptor<Integer> mUserType = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mUserFlags = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<Integer> mErrorCode = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Long> mElapsedTime = ArgumentCaptor.forClass(Long.class);
public void captureAndAssert(UserJourneyLogger mUserJourneyLogger,
long sessionId, int journey, int originalUserId,
@@ -518,7 +541,8 @@
mTargetUserId.capture(),
mUserType.capture(),
mUserFlags.capture(),
- mErrorCode.capture());
+ mErrorCode.capture(),
+ mElapsedTime.capture());
assertThat(mSessionId.getValue()).isEqualTo(sessionId);
assertThat(mJourney.getValue()).isEqualTo(journey);
@@ -577,4 +601,4 @@
state, errorCode, 1);
}
}
-}
+}
\ No newline at end of file
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/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 8ac6b0f..5ec3604 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -301,6 +301,33 @@
}
@Test
+ public void testSwitchDecorInsets() {
+ createNavBarWithProvidedInsets(mDisplayContent);
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ final DisplayInfo info = mDisplayContent.getDisplayInfo();
+ final int w = info.logicalWidth;
+ final int h = info.logicalHeight;
+ displayPolicy.updateDecorInsetsInfo();
+ final Rect prevConfigFrame = new Rect(displayPolicy.getDecorInsetsInfo(info.rotation,
+ info.logicalWidth, info.logicalHeight).mConfigFrame);
+
+ displayPolicy.updateCachedDecorInsets();
+ mDisplayContent.updateBaseDisplayMetrics(w / 2, h / 2,
+ info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
+ // There is no previous cache. But the current state will be cached.
+ assertFalse(displayPolicy.shouldKeepCurrentDecorInsets());
+
+ // Switch to original state.
+ displayPolicy.updateCachedDecorInsets();
+ mDisplayContent.updateBaseDisplayMetrics(w, h,
+ info.logicalDensityDpi, info.physicalXDpi, info.physicalYDpi);
+ assertTrue(displayPolicy.shouldKeepCurrentDecorInsets());
+ // The current insets are restored from cache directly.
+ assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
+ info.logicalWidth, info.logicalHeight).mConfigFrame);
+ }
+
+ @Test
public void testUpdateDisplayConfigurationByDecor() {
doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt());
final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent);
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 03c93e9..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;
@@ -24,6 +26,7 @@
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO;
import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
@@ -40,7 +43,9 @@
import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
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;
@@ -453,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());
@@ -861,6 +889,174 @@
}
@Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_overrideEnabled_returnsTrue() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyTrue_overrideEnabled_returnsTrue()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyTrue_overrideDisabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ true);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_overrideDisabled_returnsFalse() {
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyFalse_overrideEnabled_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @Test
+ @DisableCompatChanges({OVERRIDE_MIN_ASPECT_RATIO})
+ public void testshouldOverrideMinAspectRatio_propertyFalse_noOverride_returnsFalse()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE, /* value */ false);
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldOverrideMinAspectRatio());
+ }
+
+ @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/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index f70268e..e793f31 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -168,9 +168,18 @@
* Attached to the HAL service via factory.
*/
private void attachToHal() {
- mHalService = new SoundTriggerHalEnforcer(
- new SoundTriggerHalWatchdog(
- new SoundTriggerDuplicateModelHandler(mHalFactory.create())));
+ mHalService = null;
+ while (mHalService == null) {
+ try {
+ mHalService = new SoundTriggerHalEnforcer(
+ new SoundTriggerHalWatchdog(
+ new SoundTriggerDuplicateModelHandler(mHalFactory.create())));
+ } catch (RuntimeException e) {
+ if (!(e.getCause() instanceof RemoteException)) {
+ throw e;
+ }
+ }
+ }
mHalService.linkToDeath(this);
mHalService.registerCallback(this);
mProperties = mHalService.getProperties();
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)