diff options
313 files changed, 8570 insertions, 2527 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 72e66450d339..e4e3de270aee 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1443,6 +1443,9 @@ public class JobSchedulerService extends com.android.server.SystemService if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) { Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString() + " -- package not allowed to start"); + Counter.logIncrementWithUid( + "job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled", + uId); return JobScheduler.RESULT_FAILURE; } @@ -1519,6 +1522,9 @@ public class JobSchedulerService extends com.android.server.SystemService if ((mConstants.USE_TARE_POLICY && !mTareController.canScheduleEJ(jobStatus)) || (!mConstants.USE_TARE_POLICY && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) { + Counter.logIncrementWithUid( + "job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota", + uId); return JobScheduler.RESULT_FAILURE; } } @@ -4083,6 +4089,9 @@ public class JobSchedulerService extends com.android.server.SystemService if (!isInStateToScheduleUiJobSource && !isInStateToScheduleUiJobCalling) { Slog.e(TAG, "Uid(s) " + sourceUid + "/" + callingUid + " not in a state to schedule user-initiated jobs"); + Counter.logIncrementWithUid( + "job_scheduler.value_cntr_w_uid_schedule_failure_uij_invalid_state", + callingUid); return JobScheduler.RESULT_FAILURE; } } @@ -4124,6 +4133,10 @@ public class JobSchedulerService extends com.android.server.SystemService if (namespace.isEmpty()) { throw new IllegalArgumentException("namespace cannot be empty"); } + if (namespace.length() > 1000) { + throw new IllegalArgumentException( + "namespace cannot be more than 1000 characters"); + } namespace = namespace.intern(); } return namespace; @@ -4132,10 +4145,14 @@ public class JobSchedulerService extends com.android.server.SystemService private int validateRunUserInitiatedJobsPermission(int uid, String packageName) { final int state = getRunUserInitiatedJobsPermissionState(uid, packageName); if (state == PermissionChecker.PERMISSION_HARD_DENIED) { + Counter.logIncrementWithUid( + "job_scheduler.value_cntr_w_uid_schedule_failure_uij_no_permission", uid); throw new SecurityException(android.Manifest.permission.RUN_USER_INITIATED_JOBS + " required to schedule user-initiated jobs."); } if (state == PermissionChecker.PERMISSION_SOFT_DENIED) { + Counter.logIncrementWithUid( + "job_scheduler.value_cntr_w_uid_schedule_failure_uij_no_permission", uid); return JobScheduler.RESULT_FAILURE; } return JobScheduler.RESULT_SUCCESS; diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index b6dc32a29f04..7d09b992d080 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -253,7 +253,7 @@ public class Bmgr { try { boolean enable = Boolean.parseBoolean(arg); - mBmgr.setAutoRestore(enable); + mBmgr.setAutoRestoreForUser(userId, enable); System.out.println( "Auto restore is now " + (enable ? "enabled" : "disabled") diff --git a/cmds/bu/src/com/android/commands/bu/Backup.java b/cmds/bu/src/com/android/commands/bu/Backup.java index 373677eccf62..11c9773a7a6c 100644 --- a/cmds/bu/src/com/android/commands/bu/Backup.java +++ b/cmds/bu/src/com/android/commands/bu/Backup.java @@ -56,6 +56,7 @@ public final class Backup { } public void run(String[] args) { + Log.d(TAG, "Called run() with args: " + String.join(" ", args)); if (mBackupManager == null) { Log.e(TAG, "Can't obtain Backup Manager binder"); return; @@ -70,6 +71,8 @@ public final class Backup { return; } + Log.d(TAG, "UserId : " + userId); + String arg = nextArg(); if (arg.equals("backup")) { doBackup(OsConstants.STDOUT_FILENO, userId); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e2ef00525902..b5ee895a5a01 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4433,6 +4433,21 @@ public class ActivityManager { } /** + * Similar to {@link #forceStopPackageAsUser(String, int)} but will also stop the package even + * when the user is in the stopping state. + * + * @hide + */ + @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES) + public void forceStopPackageAsUserEvenWhenStopping(String packageName, @UserIdInt int userId) { + try { + getService().forceStopPackageEvenWhenStopping(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the current locales of the device. Calling app must have the permission * {@code android.permission.CHANGE_CONFIGURATION} and * {@code android.permission.WRITE_SETTINGS}. diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index e9fbf6b178fb..e15e08fc0ef0 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -336,6 +336,7 @@ interface IActivityManager { boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback); @UnsupportedAppUsage void forceStopPackage(in String packageName, int userId); + void forceStopPackageEvenWhenStopping(in String packageName, int userId); boolean killPids(in int[] pids, in String reason, boolean secure); @UnsupportedAppUsage List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e53680f23a9f..df9257c1b18a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2887,8 +2887,9 @@ public class Notification implements Parcelable visitor.accept(person.getIconUri()); } - final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) - extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); + final RemoteInputHistoryItem[] history = extras.getParcelableArray( + Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, + RemoteInputHistoryItem.class); if (history != null) { for (int i = 0; i < history.length; i++) { RemoteInputHistoryItem item = history[i]; @@ -2900,7 +2901,8 @@ public class Notification implements Parcelable } if (isStyle(MessagingStyle.class) && extras != null) { - final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { @@ -2913,7 +2915,8 @@ public class Notification implements Parcelable } } - final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); + final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(historic)) { for (MessagingStyle.Message message : MessagingStyle.Message .getMessagesFromBundleArray(historic)) { @@ -2925,14 +2928,16 @@ public class Notification implements Parcelable } } } + + visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class)); } if (isStyle(CallStyle.class) & extras != null) { - Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); + Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); if (callPerson != null) { visitor.accept(callPerson.getIconUri()); } - visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); + visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); } if (mBubbleMetadata != null) { @@ -3407,7 +3412,7 @@ public class Notification implements Parcelable * separate object, replace it with the field's version to avoid holding duplicate copies. */ private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { - if (original != null && extras.getParcelable(extraName) != null) { + if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) { extras.putParcelable(extraName, original); } } @@ -7084,7 +7089,8 @@ public class Notification implements Parcelable */ public boolean hasImage() { if (isStyle(MessagingStyle.class) && extras != null) { - final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, + Parcelable.class); if (!ArrayUtils.isEmpty(messages)) { for (MessagingStyle.Message m : MessagingStyle.Message .getMessagesFromBundleArray(messages)) { @@ -8286,15 +8292,18 @@ public class Notification implements Parcelable protected void restoreFromExtras(Bundle extras) { super.restoreFromExtras(extras); - mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); - if (mUser == null) { + Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); + if (user == null) { CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); mUser = new Person.Builder().setName(displayName).build(); + } else { + mUser = user; } mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); - Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); + Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class); mMessages = Message.getMessagesFromBundleArray(messages); - Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); + Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, + Parcelable.class); mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); @@ -11962,7 +11971,8 @@ public class Notification implements Parcelable if (b == null) { return null; } - Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); + Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, + Parcelable.class); String[] messages = null; if (parcelableMessages != null) { String[] tmp = new String[parcelableMessages.length]; @@ -12299,7 +12309,7 @@ public class Notification implements Parcelable @Nullable private static <T extends Parcelable> T[] getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass) { - final Parcelable[] array = bundle.getParcelableArray(key); + final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class); final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); if (arrayClass.isInstance(array) || array == null) { return (T[]) array; diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index c0106ab0bc11..1603cd9e2c81 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -2912,21 +2912,62 @@ public class WallpaperManager { } } - // Check if the package exists - if (cn != null) { - try { - final PackageManager packageManager = context.getPackageManager(); - packageManager.getPackageInfo(cn.getPackageName(), - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } catch (PackageManager.NameNotFoundException e) { - cn = null; + if (!isComponentExist(context, cn)) { + cn = null; + } + + return cn; + } + + /** + * Return {@link ComponentName} of the CMF default wallpaper, or + * {@link #getDefaultWallpaperComponent(Context)} if none is defined. + * + * @hide + */ + public static ComponentName getCmfDefaultWallpaperComponent(Context context) { + ComponentName cn = null; + String[] cmfWallpaperMap = context.getResources().getStringArray( + com.android.internal.R.array.cmf_default_wallpaper_component); + if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) { + Log.d(TAG, "No CMF wallpaper config"); + return getDefaultWallpaperComponent(context); + } + + for (String entry : cmfWallpaperMap) { + String[] cmfWallpaper; + if (!TextUtils.isEmpty(entry)) { + cmfWallpaper = entry.split(","); + if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals( + cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) { + cn = ComponentName.unflattenFromString(cmfWallpaper[1]); + break; + } } } + if (!isComponentExist(context, cn)) { + cn = null; + } + return cn; } + private static boolean isComponentExist(Context context, ComponentName cn) { + if (cn == null) { + return false; + } + try { + final PackageManager packageManager = context.getPackageManager(); + packageManager.getPackageInfo(cn.getPackageName(), + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + return true; + } + /** * Register a callback for lock wallpaper observation. Only the OS may use this. * diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index e59901b24a65..e9fb8110b4b2 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -4040,8 +4040,7 @@ public class DevicePolicyManager { public static @interface MtePolicy {} /** - * Called by a device owner, profile owner of an organization-owned device, or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MTE} permission to set the Memory + * Called by a device owner, profile owner of an organization-owned device, to set the Memory * Tagging Extension (MTE) policy. MTE is a CPU extension that allows to protect against certain * classes of security problems at a small runtime performance cost overhead. * @@ -4067,8 +4066,7 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned device, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MTE} permission to + * Called by a device owner, profile owner of an organization-owned device to * get the Memory Tagging Extension (MTE) policy * * <a href="https://source.android.com/docs/security/test/memory-safety/arm-mte"> @@ -5278,9 +5276,7 @@ public class DevicePolicyManager { } /** - * Called by a device admin or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission to set - * the password expiration timeout. Calling this method will + * Called by a device admin to set the password expiration timeout. Calling this method will * restart the countdown for password expiration for the given admin, as will changing the * device password (for all admins). * <p> @@ -5309,10 +5305,7 @@ public class DevicePolicyManager { * @param timeout The limit (in ms) that a password can remain in effect. A value of 0 means * there is no restriction (unlimited). * @throws SecurityException if {@code admin} is not an active administrator or {@code admin} - * does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} and the caller - * does not hold the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} - * permission + * does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true) @@ -5476,8 +5469,7 @@ public class DevicePolicyManager { * * @return {@code true} if the password meets the policy requirements, {@code false} otherwise * @throws SecurityException if the calling application isn't an active admin that uses - * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} and does not hold the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission + * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} * @throws IllegalStateException if the user isn't unlocked */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true) @@ -5545,8 +5537,7 @@ public class DevicePolicyManager { * <p>Note that when called from a profile which uses an unified challenge with its parent, the * screen lock complexity of the parent will be returned. * - * <p>Apps need the {@link permission#REQUEST_PASSWORD_COMPLEXITY} or - * {@link permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permissions to call this + * <p>Apps need the {@link permission#REQUEST_PASSWORD_COMPLEXITY} permission to call this * method. On Android {@link android.os.Build.VERSION_CODES#S} and above, the calling * application does not need this permission if it is a device owner or a profile owner. * @@ -5556,9 +5547,8 @@ public class DevicePolicyManager { * * @throws IllegalStateException if the user is not unlocked. * @throws SecurityException if the calling application does not have the permission - * {@link permission#REQUEST_PASSWORD_COMPLEXITY} or - * {@link permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}, and - * is not a device owner or a profile owner. + * {@link permission#REQUEST_PASSWORD_COMPLEXITY}, and is not a + * device owner or a profile owner. */ @PasswordComplexity @RequiresPermission(anyOf={MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, REQUEST_PASSWORD_COMPLEXITY}, conditional = true) @@ -5595,9 +5585,8 @@ public class DevicePolicyManager { * with {@link #PASSWORD_QUALITY_UNSPECIFIED} on that instance prior to setting complexity * requirement for the managed profile. * - * @throws SecurityException if the calling application is not a device owner, a profile - * owner, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission. + * @throws SecurityException if the calling application is not a device owner or a profile + * owner. * @throws IllegalArgumentException if the complexity level is not one of the four above. * @throws IllegalStateException if the caller is trying to set password complexity while there * are password requirements specified using {@link #setPasswordQuality(ComponentName, int)} @@ -5631,8 +5620,7 @@ public class DevicePolicyManager { * restrictions on the parent profile. * * @throws SecurityException if the calling application is not a device owner or a profile - * owner and does not hold the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission. + * owner. */ @PasswordComplexity @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true) @@ -5744,8 +5732,7 @@ public class DevicePolicyManager { * @return The number of times user has entered an incorrect password since the last correct * password entry. * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and does not hold the - * @link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission. + * that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true) @@ -5816,9 +5803,6 @@ public class DevicePolicyManager { * profile. * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the * password is always empty and this method has no effect - i.e. the policy is not set. - * <p> - * This policy can be set by holders of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIPE_DATA} permission. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. @@ -5991,11 +5975,9 @@ public class DevicePolicyManager { } /** - * Called by a profile owner, device owner or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD} to provision a token - * which can later be used to reset the device lockscreen password (if called by on the main or - * system user), or managed profile challenge (if called on a managed profile), via - * {@link #resetPasswordWithToken}. + * Called by a profile or device owner to provision a token which can later be used to reset the + * device lockscreen password (if called by device owner), or managed profile challenge (if + * called by profile owner), via {@link #resetPasswordWithToken}. * <p> * If the user currently has a lockscreen password, the provisioned token will not be * immediately usable; it only becomes active after the user performs a confirm credential @@ -6023,9 +6005,7 @@ public class DevicePolicyManager { * @param token a secure token a least 32-byte long, which must be generated by a * cryptographically strong random number generator. * @return true if the operation is successful, false otherwise. - * @throws SecurityException if admin is not a device or profile owner and the caller does - * not hold the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}. + * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalArgumentException if the supplied token is invalid. */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @@ -6101,10 +6081,8 @@ public class DevicePolicyManager { } /** - * Called by device owner, profile owner or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}to force set a new - * device unlock password or a managed profile challenge on current user. This takes effect - * immediately. + * Called by device or profile owner to force set a new device unlock password or a managed + * profile challenge on current user. This takes effect immediately. * <p> * Unlike {@link #resetPassword}, this API can change the password even before the user or * device is unlocked or decrypted. The supplied token must have been previously provisioned via @@ -6131,8 +6109,7 @@ public class DevicePolicyManager { * {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}. * @return Returns true if the password was applied, or false if it is not acceptable for the * current constraints. - * @throws SecurityException if admin is not a device or profile owner and the caller does not - * hold the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}. + * @throws SecurityException if admin is not a device or profile owner. * @throws IllegalStateException if the provided token is not valid. */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @@ -6168,8 +6145,7 @@ public class DevicePolicyManager { * @param timeMs The new desired maximum time to lock in milliseconds. A value of 0 means there * is no restriction. * @throws SecurityException if {@code admin} is not an active administrator or it does not use - * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the caller does not hold the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission + * {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true) public void setMaximumTimeToLock(@Nullable ComponentName admin, long timeMs) { @@ -6214,9 +6190,7 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner, or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission to set - * the timeout after which unlocking with secondary, non + * Called by a device/profile owner to set the timeout after which unlocking with secondary, non * strong auth (e.g. fingerprint, face, trust agents) times out, i.e. the user has to use a * strong authentication method like password, pin or pattern. * @@ -6247,8 +6221,7 @@ public class DevicePolicyManager { * auth at all times using {@link #KEYGUARD_DISABLE_FINGERPRINT} and/or * {@link #KEYGUARD_DISABLE_TRUST_AGENTS}. * - * @throws SecurityException if {@code admin} is not permitted to set this policy. - * + * @throws SecurityException if {@code admin} is not a device or profile owner. */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true) @@ -6325,8 +6298,7 @@ public class DevicePolicyManager { * <p> * This method secures the device in response to an urgent situation, such as a lost or stolen * device. After this method is called, the device must be unlocked using strong authentication - * (PIN, pattern, or password). This API is for use only by device admins and holders of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission. + * (PIN, pattern, or password). This API is intended for use only by device admins. * <p> * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is @@ -6350,8 +6322,7 @@ public class DevicePolicyManager { * Equivalent to calling {@link #lockNow(int)} with no flags. * * @throws SecurityException if the calling application does not own an active administrator - * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and does not hold the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission + * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true) public void lockNow() { @@ -6563,8 +6534,7 @@ public class DevicePolicyManager { } /** - * Callable by device owner, profile owner of an organization-owned device, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} permission to set a + * Callable by device owner or profile owner of an organization-owned device, to set a * factory reset protection (FRP) policy. When a new policy is set, the system * notifies the FRP management agent of a policy change by broadcasting * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}. @@ -6572,9 +6542,8 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin * @param policy the new FRP policy, or {@code null} to clear the current policy. - * @throws SecurityException if {@code admin} is not a device owner, profile owner of - * an organization-owned device, or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} permission + * @throws SecurityException if {@code admin} is not a device owner or a profile owner of + * an organization-owned device. * @throws UnsupportedOperationException if factory reset protection is not * supported on the device. */ @@ -6592,10 +6561,9 @@ public class DevicePolicyManager { } /** - * Callable by device owner, profile owner of an organization-owned device, or - * holder of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} - * permission to retrieve the current factory reset protection (FRP) - * policy set previously by {@link #setFactoryResetProtectionPolicy}. + * Callable by device owner or profile owner of an organization-owned device, to retrieve + * the current factory reset protection (FRP) policy set previously by + * {@link #setFactoryResetProtectionPolicy}. * <p> * This method can also be called by the FRP management agent on device or with the permission * {@link android.Manifest.permission#MASTER_CLEAR}, in which case, it can pass {@code null} @@ -6605,9 +6573,7 @@ public class DevicePolicyManager { * {@code null} if the caller is not a device admin * @return The current FRP policy object or {@code null} if no policy is set. * @throws SecurityException if {@code admin} is not a device owner, a profile owner of - * an organization-owned device, a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} - * permission, or the FRP management agent. + * an organization-owned device or the FRP management agent. * @throws UnsupportedOperationException if factory reset protection is not * supported on the device. */ @@ -7541,8 +7507,6 @@ public class DevicePolicyManager { * <li>Profile owner</li> * <li>Delegated certificate installer</li> * <li>Credential management app</li> - * <li>An app that holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission</li> * </ul> * * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app @@ -7553,10 +7517,9 @@ public class DevicePolicyManager { * {@code null} if the caller is not a device admin. * @param alias The private key alias under which the certificate is installed. * @return {@code true} if the private key alias no longer exists, {@code false} otherwise. - * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or - * profile owner, or {@code admin} is null but the calling application is not a - * delegated certificate installer, credential management app and does not have the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. + * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile + * owner, or {@code admin} is null but the calling application is not a delegated + * certificate installer or credential management app. * @see #setDelegatedScopes * @see #DELEGATION_CERT_INSTALL */ @@ -7643,23 +7606,19 @@ public class DevicePolicyManager { * supports these features, refer to {@link #isDeviceIdAttestationSupported()} and * {@link #isUniqueDeviceAttestationSupported()}. * - * <p>Device owner, profile owner, their delegated certificate installer, the credential - * management app or an app that holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can use - * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information including - * manufacturer, model, brand, device and product in the attestation record. - * Only device owner, profile owner on an organization-owned device or affiliated user, their - * delegated certificate installers or an app that holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can use - * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request unique - * device identifiers to be attested (the serial number, IMEI and MEID correspondingly), - * if supported by the device (see {@link #isDeviceIdAttestationSupported()}). - * Additionally, device owner, profile owner on an organization-owned device, their delegated - * certificate installers and an app that holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can also - * request the attestation record to be signed using an individual attestation certificate by - * specifying the {@link #ID_TYPE_INDIVIDUAL_ATTESTATION} flag (if supported by the device, - * see {@link #isUniqueDeviceAttestationSupported()}). + * <p>Device owner, profile owner, their delegated certificate installer and the credential + * management app can use {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device + * information including manufacturer, model, brand, device and product in the attestation + * record. + * Only device owner, profile owner on an organization-owned device or affiliated user, and + * their delegated certificate installers can use {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} + * and {@link #ID_TYPE_MEID} to request unique device identifiers to be attested (the serial + * number, IMEI and MEID correspondingly), if supported by the device + * (see {@link #isDeviceIdAttestationSupported()}). + * Additionally, device owner, profile owner on an organization-owned device and their delegated + * certificate installers can also request the attestation record to be signed using an + * individual attestation certificate by specifying the {@link #ID_TYPE_INDIVIDUAL_ATTESTATION} + * flag (if supported by the device, see {@link #isUniqueDeviceAttestationSupported()}). * <p> * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set. @@ -7684,14 +7643,12 @@ public class DevicePolicyManager { * If any flag is specified, then an attestation challenge must be included in the * {@code keySpec}. * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise. - * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or - * profile owner, or {@code admin} is null but the calling application is not a - * delegated certificate installer, credential management app and does not have the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. - * If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL}, - * {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner, - * the Certificate Installer delegate or have the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. + * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile + * owner, or {@code admin} is null but the calling application is not a delegated + * certificate installer or credential management app. If Device ID attestation is + * requested (using {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} or + * {@link #ID_TYPE_MEID}), the caller must be the Device Owner or the Certificate + * Installer delegate. * @throws IllegalArgumentException in the following cases: * <p> * <ul> @@ -7974,8 +7931,6 @@ public class DevicePolicyManager { * <li>Profile owner</li> * <li>Delegated certificate installer</li> * <li>Credential management app</li> - * <li>An app that holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission</li> * </ul> * * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app @@ -7996,10 +7951,9 @@ public class DevicePolicyManager { * {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}. * @return {@code true} if the provided {@code alias} exists and the certificates has been * successfully associated with it, {@code false} otherwise. - * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or - * profile owner, or {@code admin} is null but the calling application is not a - * delegated certificate installer, credential management app and does not have the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. + * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile + * owner, or {@code admin} is null but the calling application is not a delegated + * certificate installer or credential management app. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true) public boolean setKeyPairCertificate(@Nullable ComponentName admin, @@ -8387,7 +8341,7 @@ public class DevicePolicyManager { * <p> * This method can be called on the {@link DevicePolicyManager} instance, * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be - * the profile owner of an organization-owned managed profile + * the profile owner of an organization-owned managed profile. * <p> * If the caller is device owner, then the restriction will be applied to all users. If * called on the parent instance, then the restriction will be applied on the personal profile. @@ -8430,9 +8384,7 @@ public class DevicePolicyManager { * <p> * This method can be called on the {@link DevicePolicyManager} instance, * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be - * the profile owner of an organization-owned managed profile or the caller has been granted - * the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA} and the - * cross-user permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}. + * the profile owner of an organization-owned managed profile. * * @param admin The name of the admin component to check, or {@code null} to check whether any * admins have disabled the camera @@ -8483,11 +8435,9 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner, or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CAPTURE} permission to set - * whether the screen capture is disabled. Disabling screen capture also prevents the - * content from being shown on display devices that do not have a secure video output. - * See {@link android.view.Display#FLAG_SECURE} for more details about + * Called by a device/profile owner to set whether the screen capture is disabled. Disabling + * screen capture also prevents the content from being shown on display devices that do not have + * a secure video output. See {@link android.view.Display#FLAG_SECURE} for more details about * secure surfaces and secure displays. * <p> * This method can be called on the {@link DevicePolicyManager} instance, returned by @@ -8696,10 +8646,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, a profile owner for the primary user, a profile - * owner of an organization-owned managed profile or, starting from Android - * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission - * {@link android.Manifest.permission#SET_TIME} to turn auto time on and off. + * Called by a device owner, a profile owner for the primary user or a profile + * owner of an organization-owned managed profile to turn auto time on and off. * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} * to prevent the user from changing this setting. * <p> @@ -8711,8 +8659,7 @@ public class DevicePolicyManager { * caller is not a device admin. * @param enabled Whether time should be obtained automatically from the network or not. * @throws SecurityException if caller is not a device owner, a profile owner for the - * primary user, or a profile owner of an organization-owned managed profile or a holder of the - * permission {@link android.Manifest.permission#SET_TIME}. + * primary user, or a profile owner of an organization-owned managed profile. */ @RequiresPermission(value = SET_TIME, conditional = true) public void setAutoTimeEnabled(@Nullable ComponentName admin, boolean enabled) { @@ -8729,19 +8676,15 @@ public class DevicePolicyManager { /** * Returns true if auto time is enabled on the device. * - * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers - * are also able to call this method if they hold the permission - *{@link android.Manifest.permission#SET_TIME}. - * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. * @return true if auto time is enabled on the device. - * @throws SecurityException if the caller is not a device owner, a profile - * owner for the primary user, or a profile owner of an organization-owned managed profile or a - * holder of the permission {@link android.Manifest.permission#SET_TIME}. + * @throws SecurityException if caller is not a device owner, a profile owner for the + * primary user, or a profile owner of an organization-owned managed profile. */ @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true) public boolean getAutoTimeEnabled(@Nullable ComponentName admin) { + throwIfParentInstance("getAutoTimeEnabled"); if (mService != null) { try { return mService.getAutoTimeEnabled(admin, mContext.getPackageName()); @@ -8753,10 +8696,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, a profile owner for the primary user, a profile - * owner of an organization-owned managed profile or, starting from Android - * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission - * {@link android.Manifest.permission#SET_TIME} to turn auto time zone on and off. + * Called by a device owner, a profile owner for the primary user or a profile + * owner of an organization-owned managed profile to turn auto time zone on and off. * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} * to prevent the user from changing this setting. * <p> @@ -8768,8 +8709,7 @@ public class DevicePolicyManager { * caller is not a device admin. * @param enabled Whether time zone should be obtained automatically from the network or not. * @throws SecurityException if caller is not a device owner, a profile owner for the - * primary user, or a profile owner of an organization-owned managed profile or a holder of the - * permission {@link android.Manifest.permission#SET_TIME_ZONE}. + * primary user, or a profile owner of an organization-owned managed profile. */ @SupportsCoexistence @RequiresPermission(value = SET_TIME_ZONE, conditional = true) @@ -8787,16 +8727,11 @@ public class DevicePolicyManager { /** * Returns true if auto time zone is enabled on the device. * - * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers - * are also able to call this method if they hold the permission - *{@link android.Manifest.permission#SET_TIME}. - * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. * @return true if auto time zone is enabled on the device. - * @throws SecurityException if the caller is not a device owner, a profile - * owner for the primary user, or a profile owner of an organization-owned managed profile or a - * holder of the permission {@link android.Manifest.permission#SET_TIME_ZONE}. + * @throws SecurityException if caller is not a device owner, a profile owner for the + * primary user, or a profile owner of an organization-owned managed profile. */ @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true) public boolean getAutoTimeZoneEnabled(@Nullable ComponentName admin) { @@ -8906,8 +8841,7 @@ public class DevicePolicyManager { * {@link #KEYGUARD_DISABLE_IRIS}, * {@link #KEYGUARD_DISABLE_SHORTCUTS_ALL}. * @throws SecurityException if {@code admin} is not an active administrator or does not use - * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} and does not hold - * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_KEYGUARD} permission + * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_KEYGUARD, conditional = true) public void setKeyguardDisabledFeatures(@Nullable ComponentName admin, int which) { @@ -9524,12 +9458,9 @@ public class DevicePolicyManager { } /** - * Called by device or profile owners or holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}. - * to suspend packages for this user. This function can be - * called by a device owner, profile owner, by a delegate given the - * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes} or by holders of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}. + * Called by device or profile owners to suspend packages for this user. This function can be + * called by a device owner, profile owner, or by a delegate given the + * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes}. * <p> * A suspended package will not be able to start activities. Its notifications will be hidden, * it will not show up in recents, will not be able to show toasts or dialogs or ring the @@ -9550,9 +9481,7 @@ public class DevicePolicyManager { * {@code false} the packages will be unsuspended. * @return an array of package names for which the suspended status is not set as requested in * this method. - * @throws SecurityException if {@code admin} is not a device or profile owner or has not been - * granted the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}. + * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #setDelegatedScopes * @see #DELEGATION_PACKAGE_ACCESS */ @@ -9912,9 +9841,7 @@ public class DevicePolicyManager { /** * Must be called by a device owner or a profile owner of an organization-owned managed profile - * or holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEFAULT_SMS} to set the default SMS - * application. + * to set the default SMS application. * <p> * This method can be called on the {@link DevicePolicyManager} instance, returned by * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner @@ -9930,11 +9857,9 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. * @param packageName The name of the package to set as the default SMS application. - * @throws SecurityException if {@code admin} is not a device or profile owner or if - * called on the parent profile and the {@code admin} is not a - * profile owner of an organization-owned managed profile and - * if the caller has not been granted the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEFAULT_SMS}. + * @throws SecurityException if {@code admin} is not a device or profile owner or if + * called on the parent profile and the {@code admin} is not a + * 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. */ @@ -10157,8 +10082,7 @@ public class DevicePolicyManager { * documentation of the specific trust agent to determine the interpretation of this * bundle. * @throws SecurityException if {@code admin} is not an active administrator or does not use - * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} and does not have - * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_KEYGUARD} permission + * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} */ @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN) @RequiresPermission(value = MANAGE_DEVICE_POLICY_KEYGUARD, conditional = true) @@ -10693,20 +10617,16 @@ public class DevicePolicyManager { } /** - * Called by the profile owner of a managed profile or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}. so that some - * intents sent in the managed profile can also be resolved in the parent, or vice versa. - * Only activity intents are supported. + * Called by the profile owner of a managed profile so that some intents sent in the managed + * profile can also be resolved in the parent, or vice versa. Only activity intents are + * supported. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the - * caller is not a device admin. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the * other profile * @param flags {@link DevicePolicyManager#FLAG_MANAGED_CAN_ACCESS_PARENT} and * {@link DevicePolicyManager#FLAG_PARENT_CAN_ACCESS_MANAGED} are supported. - * @throws SecurityException if {@code admin} is not a device or profile owner and is not a - * holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional = true) public void addCrossProfileIntentFilter(@Nullable ComponentName admin, IntentFilter filter, @@ -10723,10 +10643,9 @@ public class DevicePolicyManager { } /** - * Called by a profile owner of a managed profile or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION} to remove the - * cross-profile intent filters that go from the managed profile to the parent, or from the - * parent to the managed profile. Only removes those that have been set by the profile owner. + * Called by a profile owner of a managed profile to remove the cross-profile intent filters + * that go from the managed profile to the parent, or from the parent to the managed profile. + * Only removes those that have been set by the profile owner. * <p> * <em>Note</em>: A list of default cross profile intent filters are set up by the system when * the profile is created, some of them ensure the proper functioning of the profile, while @@ -10735,11 +10654,8 @@ public class DevicePolicyManager { * profile data sharing is not desired, they can be disabled with * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the - * caller is not a device admin. - * @throws SecurityException if {@code admin} is not a profile owner and is not a - * holder of the permission - * @link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @throws SecurityException if {@code admin} is not a profile owner. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional = true) public void clearCrossProfileIntentFilters(@Nullable ComponentName admin) { @@ -10933,10 +10849,9 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin * @return List of input method package names. - * @throws SecurityException if {@code admin} is not a device or profile owner and does not - * hold the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INPUT_METHODS} - * permission or if called on the parent profile and the {@code admin} - * is not a profile owner of an organization-owned managed profile. + * @throws SecurityException if {@code admin} is not a device, profile owner or if called on + * the parent profile and the {@code admin} is not a profile owner + * of an organization-owned managed profile. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_INPUT_METHODS, conditional = true) public @Nullable List<String> getPermittedInputMethods(@Nullable ComponentName admin) { @@ -11766,10 +11681,9 @@ public class DevicePolicyManager { /** * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and - * actual package file remain. This function can be called by a device owner, profile owner, - * delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via - * {@link #setDelegatedScopes}, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE} permission. + * actual package file remain. This function can be called by a device owner, profile owner, or + * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via + * {@link #setDelegatedScopes}. * <p> * This method can be called on the {@link DevicePolicyManager} instance, returned by * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner @@ -11806,9 +11720,8 @@ public class DevicePolicyManager { /** * Determine if a package is hidden. This function can be called by a device owner, profile - * owner, delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via - * {@link #setDelegatedScopes}, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE} permission. + * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via + * {@link #setDelegatedScopes}. * <p> * This method can be called on the {@link DevicePolicyManager} instance, returned by * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner @@ -11922,9 +11835,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT} - * to disable account management for a specific type of account. + * Called by a device owner or profile owner to disable account management for a specific type + * of account. * <p> * The calling device admin must be a device owner or profile owner. If it is not, a security * exception will be thrown. @@ -11946,9 +11858,7 @@ public class DevicePolicyManager { * @param accountType For which account management is disabled or enabled. * @param disabled The boolean indicating that account management will be disabled (true) or * enabled (false). - * @throws SecurityException if {@code admin} is not a device or profile owner or has not been - * granted the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true) public void setAccountManagementDisabled(@Nullable ComponentName admin, String accountType, @@ -11986,10 +11896,6 @@ public class DevicePolicyManager { * @see #getAccountTypesWithManagementDisabled() * Note that calling this method on the parent profile instance will return the same * value as calling it on the main {@code DevicePolicyManager} instance. - * - * @throws SecurityException if the userId is different to the caller's and the caller has not - * been granted {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT} and - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}. * @hide */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true) @@ -12415,8 +12321,7 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to + * Called by a device owner or a profile owner of an organization-owned managed profile to * control whether the user can change networks configured by the admin. When this lockdown is * enabled, the user can still configure and connect to other Wi-Fi networks, or use other Wi-Fi * capabilities such as tethering. @@ -12431,7 +12336,8 @@ public class DevicePolicyManager { * with. Null if the caller is not a device admin. * @param lockdown Whether the admin configured networks should be unmodifiable by the * user. - * @throws SecurityException if caller is not permitted to modify this policy + * @throws SecurityException if caller is not a device owner or a profile owner of an + * organization-owned managed profile. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true) public void setConfiguredNetworksLockdownState( @@ -12448,16 +12354,13 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to + * Called by a device owner or a profile owner of an organization-owned managed profile to * determine whether the user is prevented from modifying networks configured by the admin. * * @param admin admin Which {@link DeviceAdminReceiver} this request is associated - * with. Null if the caller is not a device admin. - * @throws SecurityException if caller is not a device owner, a profile owner of an - * organization-owned managed profile, or holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} - * permission. + * with. + * @throws SecurityException if caller is not a device owner or a profile owner of an + * organization-owned managed profile. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true) public boolean hasLockdownAdminConfiguredNetworks(@Nullable ComponentName admin) { @@ -12473,20 +12376,17 @@ public class DevicePolicyManager { } /** - * Called by a device owner, a profile owner of an organization-owned managed - * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * holders of the permission {@link android.Manifest.permission#SET_TIME} to set the system wall - * clock time. This only takes effect if called when - * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be - * returned. + * Called by a device owner or a profile owner of an organization-owned managed + * profile to set the system wall clock time. This only takes effect if called when + * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} + * will be returned. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. * @param millis time in milliseconds since the Epoch * @return {@code true} if set time succeeded, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner or a profile owner - * of an organization-owned managed profile or a holder of the permission - * {@link android.Manifest.permission#SET_TIME}. + * of an organization-owned managed profile. */ @RequiresPermission(value = SET_TIME, conditional = true) public boolean setTime(@Nullable ComponentName admin, long millis) { @@ -12502,12 +12402,10 @@ public class DevicePolicyManager { } /** - * Called by a device owner, a profile owner of an organization-owned managed - * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * holders of the permission {@link android.Manifest.permission#SET_TIME_ZONE} to set the - * system's persistent default time zone. This only take effect if called when - * {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise {@code false} will be - * returned. + * Called by a device owner or a profile owner of an organization-owned managed + * profile to set the system's persistent default time zone. This only takes + * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} + * is 0, otherwise {@code false} will be returned. * * @see android.app.AlarmManager#setTimeZone(String) * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the @@ -12516,8 +12414,7 @@ public class DevicePolicyManager { * {@link java.util.TimeZone#getAvailableIDs} * @return {@code true} if set timezone succeeded, {@code false} otherwise. * @throws SecurityException if {@code admin} is not a device owner or a profile owner - * of an organization-owned managed profile or a holder of the permissions - * {@link android.Manifest.permission#SET_TIME_ZONE}. + * of an organization-owned managed profile. */ @RequiresPermission(value = SET_TIME_ZONE, conditional = true) public boolean setTimeZone(@Nullable ComponentName admin, String timeZone) { @@ -12722,9 +12619,7 @@ public class DevicePolicyManager { * @param packageName package to check. * @return true if uninstallation is blocked and the given package is visible to you, false * otherwise if uninstallation isn't blocked or the given package isn't visible to you. - * @throws SecurityException if {@code admin} is not a device or profile owner. Starting - * from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} there will not be a security - * check at all. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ public boolean isUninstallBlocked(@Nullable ComponentName admin, String packageName) { throwIfParentInstance("isUninstallBlocked"); @@ -12853,9 +12748,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owners of an organization-owned managed profile, or a - * holder of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_UPDATES} - * permission to set a local system update policy. When a new policy is set, + * Called by device owners or profile owners of an organization-owned managed profile to to set + * a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcast. * <p> * If the supplied system update policy has freeze periods set but the freeze periods do not @@ -12961,10 +12855,9 @@ public class DevicePolicyManager { } /** - * Called by device owner, profile owner of secondary users that is affiliated with the - * device or a holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_STATUS_BAR} to disable the status - * bar. Disabling the status bar blocks notifications and quick settings. + * Called by device owner or profile owner of secondary users that is affiliated with the + * device to disable the status bar. Disabling the status bar blocks notifications and quick + * settings. * <p> * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the * status bar in LockTask mode can be configured with @@ -12979,9 +12872,8 @@ public class DevicePolicyManager { * caller is not a device admin. * @param disabled {@code true} disables the status bar, {@code false} reenables it. * @return {@code false} if attempting to disable the status bar failed. {@code true} otherwise. - * @throws SecurityException if {@code admin} is not the device owner, a profile owner of - * secondary user that is affiliated with the device or if the caller is not a holder of - * the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_STATUS_BAR}. + * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of + * secondary user that is affiliated with the device. * @see #isAffiliatedUser * @see #getSecondaryUsers */ @@ -13155,8 +13047,7 @@ public class DevicePolicyManager { * cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which * the permission is granted and the user cannot manage it through the UI. This method can only * be called by a profile owner, device owner, or a delegate given the - * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes} or holders of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS} . + * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}. * <p/> * Note that user cannot manage other permissions in the affected group through the UI * either and their granted state will be kept as the current value. Thus, it's recommended that @@ -13227,8 +13118,7 @@ public class DevicePolicyManager { * {@link #PERMISSION_GRANT_STATE_DENIED}, {@link #PERMISSION_GRANT_STATE_DEFAULT}, * {@link #PERMISSION_GRANT_STATE_GRANTED}, * @return whether the permission was successfully granted or revoked. - * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS}. + * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #PERMISSION_GRANT_STATE_DENIED * @see #PERMISSION_GRANT_STATE_DEFAULT * @see #PERMISSION_GRANT_STATE_GRANTED @@ -13278,8 +13168,7 @@ public class DevicePolicyManager { * be one of {@link #PERMISSION_GRANT_STATE_DENIED} or * {@link #PERMISSION_GRANT_STATE_GRANTED}, which indicates if the permission is * currently denied or granted. - * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS}. + * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #setPermissionGrantState(ComponentName, String, String, int) * @see PackageManager#checkPermission(String, String) * @see #setDelegatedScopes @@ -13400,12 +13289,11 @@ public class DevicePolicyManager { } /** - * Called by a device admin or holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE} to set the short - * support message. This will be displayed to the user in settings screens where functionality - * has been disabled by the admin. The message should be limited to a short statement such as - * "This setting is disabled by your administrator. Contact someone@example.com for support." - * If the message is longer than 200 characters it may be truncated. + * Called by a device admin to set the short support message. This will be displayed to the user + * in settings screens where functionality has been disabled by the admin. The message should be + * limited to a short statement such as "This setting is disabled by your administrator. Contact + * someone@example.com for support." If the message is longer than 200 characters it may be + * truncated. * <p> * If the short support message needs to be localized, it is the responsibility of the * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast @@ -13416,9 +13304,7 @@ public class DevicePolicyManager { * caller is not a device admin. * @param message Short message to be displayed to the user in settings or null to clear the * existing message. - * @throws SecurityException if {@code admin} is not an active administrator and is not a - * holder of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE}. + * @throws SecurityException if {@code admin} is not an active administrator. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, conditional = true) public void setShortSupportMessage(@Nullable ComponentName admin, @@ -13628,9 +13514,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission - * to control the security logging feature. + * Called by device owner or a profile owner of an organization-owned managed profile to + * control the security logging feature. * * <p> Security logs contain various information intended for security auditing purposes. * When security logging is enabled by any app other than the device owner, certain security @@ -13667,10 +13552,8 @@ public class DevicePolicyManager { /** * Return whether security logging is enabled or not by the admin. * - * <p>Can only be called by a device owner, a profile owner of an organization-owned - * managed profile, or a holder of the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission - * otherwise a {@link SecurityException} will be thrown. + * <p>Can only be called by the device owner or a profile owner of an organization-owned + * managed profile, otherwise a {@link SecurityException} will be thrown. * * @param admin Which device admin this request is associated with. Null if the caller is not * a device admin @@ -13688,10 +13571,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission - * to retrieve all new security logging entries since the last call to this API after device - * boots. + * Called by device owner or profile owner of an organization-owned managed profile to retrieve + * all new security logging entries since the last call to this API after device boots. * * <p> Access to the logs is rate limited and it will only return new logs after the admin has * been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}. @@ -13845,9 +13726,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manfiest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission - * to retrieve device logs from before the device's last reboot. + * Called by device owner or profile owner of an organization-owned managed profile to retrieve + * device logs from before the device's last reboot. * <p> * <strong> This API is not supported on all devices. Calling this API on unsupported devices * will result in {@code null} being returned. The device logs are retrieved from a RAM region @@ -13977,9 +13857,8 @@ public class DevicePolicyManager { } /** - * Called by the device owner (since API 26) or profile owner (since API 24) or holders of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY} to - * set the name of the organization under management. + * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of + * the organization under management. * * <p>If the organization name needs to be localized, it is the responsibility of the caller * to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set a new version of this @@ -13988,8 +13867,7 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. * @param title The organization name or {@code null} to clear a previously set name. - * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the - * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}. + * @throws SecurityException if {@code admin} is not a device or profile owner. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, conditional = true) public void setOrganizationName(@Nullable ComponentName admin, @Nullable CharSequence title) { @@ -15248,9 +15126,8 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or a holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_UPDATES} permission to - * install a system update from the given file. The device will be + * Called by device owner or profile owner of an organization-owned managed profile to install + * a system update from the given file. The device will be * rebooted in order to finish installing the update. Note that if the device is rebooted, this * doesn't necessarily mean that the update has been applied successfully. The caller should * additionally check the system version with {@link android.os.Build#FINGERPRINT} or {@link @@ -15890,9 +15767,7 @@ public class DevicePolicyManager { } /** - * Called by device owner or profile owner of an organization-owned managed profile or - * holders of the permission - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE} to toggle + * Called by device owner or profile owner of an organization-owned managed profile to toggle * Common Criteria mode for the device. When the device is in Common Criteria mode, * certain device functionalities are tuned to meet the higher * security level required by Common Criteria certification. For example: @@ -16435,10 +16310,9 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING} - * permission to enable or disable USB data signaling for the device. When disabled, USB data - * connections (except from charging functions) are prohibited. + * Called by a device owner or profile owner of an organization-owned managed profile to enable + * or disable USB data signaling for the device. When disabled, USB data connections + * (except from charging functions) are prohibited. * * <p> This API is not supported on all devices, the caller should call * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data @@ -16584,8 +16458,7 @@ public class DevicePolicyManager { } /** - * Called by a device owner, profile owner of an organization-owned managed profile, or holder - * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to + * Called by device owner or profile owner of an organization-owned managed profile to * specify the minimum security level required for Wi-Fi networks. * The device may not connect to networks that do not meet the minimum security level. * If the current network does not meet the minimum security level set, it will be disconnected. @@ -16629,8 +16502,7 @@ public class DevicePolicyManager { } /** - * Called by device owner, profile owner of an organization-owned managed profile, or holder of - * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to + * Called by device owner or profile owner of an organization-owned managed profile to * specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}). * Wi-Fi SSID policy specifies the SSID restriction the network must satisfy * in order to be eligible for a connection. Providing a null policy results in the @@ -16658,7 +16530,8 @@ public class DevicePolicyManager { * If the policy has not been set, it will return NULL. * * @see #setWifiSsidPolicy(WifiSsidPolicy) - * @throws SecurityException if the caller is not permitted to manage wifi policy + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true) @Nullable diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 77ba560f29a1..f4dc6ba5e2de 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -1857,6 +1857,13 @@ public final class DevicePolicyResources { public static final String MINIRESOLVER_OPEN_IN_PERSONAL = PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL"; + /** + * 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 cancel. Accepts the app name as a param. + */ + public static final String MINIRESOLVER_OPEN_WORK = PREFIX + "MINIRESOLVER_OPEN_WORK"; + public static final String MINIRESOLVER_USE_WORK_BROWSER = PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL"; diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d4e231b70c3e..afe375c4ac20 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -981,6 +981,15 @@ public class PackageInstaller { * * The result is returned by a callback because some constraints might take a long time * to evaluate. + * + * @param packageNames a list of package names to check the constraints for installation + * @param constraints the constraints for installation. + * @param executor the {@link Executor} on which to invoke the callback + * @param callback called when the {@link InstallConstraintsResult} is ready + * + * @throws SecurityException if the given packages' installer of record doesn't match the + * caller's own package name or the installerPackageName set by the caller doesn't + * match the caller's own package name. */ public void checkInstallConstraints(@NonNull List<String> packageNames, @NonNull InstallConstraints constraints, @@ -1008,6 +1017,8 @@ public class PackageInstaller { * Note: the device idle constraint might take a long time to evaluate. The system will * ensure the constraint is evaluated completely before handling timeout. * + * @param packageNames a list of package names to check the constraints for installation + * @param constraints the constraints for installation. * @param callback Called when the constraints are satisfied or after timeout. * Intents sent to this callback contain: * {@link Intent#EXTRA_PACKAGES} for the input package names, @@ -1017,6 +1028,9 @@ public class PackageInstaller { * satisfied. Valid range is from 0 to one week. {@code 0} means the * callback will be invoked immediately no matter constraints are * satisfied or not. + * @throws SecurityException if the given packages' installer of record doesn't match the + * caller's own package name or the installerPackageName set by the caller doesn't + * match the caller's own package name. */ public void waitForInstallConstraints(@NonNull List<String> packageNames, @NonNull InstallConstraints constraints, @@ -1039,6 +1053,7 @@ public class PackageInstaller { * may be performed on the session. In the case of timeout, you may commit the * session again using this method or {@link Session#commit(IntentSender)} for retries. * + * @param sessionId the session ID to commit when all constraints are satisfied. * @param statusReceiver Called when the state of the session changes. Intents * sent to this receiver contain {@link #EXTRA_STATUS}. * Refer to the individual status codes on how to handle them. diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java index 629d578c7358..2508d8eb20ab 100644 --- a/core/java/android/credentials/ui/CreateCredentialProviderData.java +++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java @@ -19,7 +19,6 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; -import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -36,7 +35,7 @@ import java.util.List; @TestApi public final class CreateCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final ParceledListSlice<Entry> mSaveEntries; + private final List<Entry> mSaveEntries; @Nullable private final Entry mRemoteEntry; @@ -44,13 +43,13 @@ public final class CreateCredentialProviderData extends ProviderData implements @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mSaveEntries = new ParceledListSlice<>(saveEntries); + mSaveEntries = new ArrayList<>(saveEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getSaveEntries() { - return mSaveEntries.getList(); + return mSaveEntries; } @Nullable @@ -61,7 +60,9 @@ public final class CreateCredentialProviderData extends ProviderData implements private CreateCredentialProviderData(@NonNull Parcel in) { super(in); - mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class); + List<Entry> credentialEntries = new ArrayList<>(); + in.readTypedList(credentialEntries, Entry.CREATOR); + mSaveEntries = credentialEntries; AnnotationValidations.validate(NonNull.class, null, mSaveEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -71,7 +72,7 @@ public final class CreateCredentialProviderData extends ProviderData implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeParcelable(mSaveEntries, flags); + dest.writeTypedList(mSaveEntries); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java index 773dee97f7fe..181475c7ce5a 100644 --- a/core/java/android/credentials/ui/GetCredentialProviderData.java +++ b/core/java/android/credentials/ui/GetCredentialProviderData.java @@ -19,7 +19,6 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; -import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -36,11 +35,11 @@ import java.util.List; @TestApi public final class GetCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final ParceledListSlice<Entry> mCredentialEntries; + private final List<Entry> mCredentialEntries; @NonNull - private final ParceledListSlice<Entry> mActionChips; + private final List<Entry> mActionChips; @NonNull - private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries; + private final List<AuthenticationEntry> mAuthenticationEntries; @Nullable private final Entry mRemoteEntry; @@ -50,25 +49,25 @@ public final class GetCredentialProviderData extends ProviderData implements Par @NonNull List<AuthenticationEntry> authenticationEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mCredentialEntries = new ParceledListSlice<>(credentialEntries); - mActionChips = new ParceledListSlice<>(actionChips); - mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries); + mCredentialEntries = new ArrayList<>(credentialEntries); + mActionChips = new ArrayList<>(actionChips); + mAuthenticationEntries = new ArrayList<>(authenticationEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getCredentialEntries() { - return mCredentialEntries.getList(); + return mCredentialEntries; } @NonNull public List<Entry> getActionChips() { - return mActionChips.getList(); + return mActionChips; } @NonNull public List<AuthenticationEntry> getAuthenticationEntries() { - return mAuthenticationEntries.getList(); + return mAuthenticationEntries; } @Nullable @@ -78,16 +77,20 @@ public final class GetCredentialProviderData extends ProviderData implements Par private GetCredentialProviderData(@NonNull Parcel in) { super(in); - mCredentialEntries = in.readParcelable(null, - android.content.pm.ParceledListSlice.class); + + List<Entry> credentialEntries = new ArrayList<>(); + in.readTypedList(credentialEntries, Entry.CREATOR); + mCredentialEntries = credentialEntries; AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); - mActionChips = in.readParcelable(null, - android.content.pm.ParceledListSlice.class); + List<Entry> actionChips = new ArrayList<>(); + in.readTypedList(actionChips, Entry.CREATOR); + mActionChips = actionChips; AnnotationValidations.validate(NonNull.class, null, mActionChips); - mAuthenticationEntries = in.readParcelable(null, - android.content.pm.ParceledListSlice.class); + List<AuthenticationEntry> authenticationEntries = new ArrayList<>(); + in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR); + mAuthenticationEntries = authenticationEntries; AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -97,9 +100,9 @@ public final class GetCredentialProviderData extends ProviderData implements Par @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeParcelable(mCredentialEntries, flags); - dest.writeParcelable(mActionChips, flags); - dest.writeParcelable(mAuthenticationEntries, flags); + dest.writeTypedList(mCredentialEntries); + dest.writeTypedList(mActionChips); + dest.writeTypedList(mAuthenticationEntries); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 1bf004a2aeb0..d48e20e128b9 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -30,6 +30,7 @@ import android.hardware.camera2.extension.IInitializeSessionCallback; import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; +import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -47,8 +48,10 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Future; @@ -161,16 +164,19 @@ public final class CameraExtensionCharacteristics { private final Context mContext; private final String mCameraId; - private final CameraCharacteristics mChars; + private final Map<String, CameraCharacteristics> mCharacteristicsMap; + private final Map<String, CameraMetadataNative> mCharacteristicsMapNative; /** * @hide */ public CameraExtensionCharacteristics(Context context, String cameraId, - CameraCharacteristics chars) { + Map<String, CameraCharacteristics> characteristicsMap) { mContext = context; mCameraId = cameraId; - mChars = chars; + mCharacteristicsMap = characteristicsMap; + mCharacteristicsMapNative = + CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap); } private static ArrayList<Size> getSupportedSizes(List<SizeList> sizesList, @@ -468,11 +474,11 @@ public final class CameraExtensionCharacteristics { * @hide */ public static boolean isExtensionSupported(String cameraId, int extensionType, - CameraCharacteristics chars) { + Map<String, CameraMetadataNative> characteristicsMap) { if (areAdvancedExtensionsSupported()) { try { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extensionType); - return extender.isExtensionAvailable(cameraId); + return extender.isExtensionAvailable(cameraId, characteristicsMap); } catch (RemoteException e) { Log.e(TAG, "Failed to query extension availability! Extension service does not" + " respond!"); @@ -487,8 +493,10 @@ public final class CameraExtensionCharacteristics { } try { - return extenders.first.isExtensionAvailable(cameraId, chars.getNativeMetadata()) && - extenders.second.isExtensionAvailable(cameraId, chars.getNativeMetadata()); + return extenders.first.isExtensionAvailable(cameraId, + characteristicsMap.get(cameraId)) + && extenders.second.isExtensionAvailable(cameraId, + characteristicsMap.get(cameraId)); } catch (RemoteException e) { Log.e(TAG, "Failed to query extension availability! Extension service does not" + " respond!"); @@ -563,7 +571,7 @@ public final class CameraExtensionCharacteristics { try { for (int extensionType : EXTENSION_LIST) { - if (isExtensionSupported(mCameraId, extensionType, mChars)) { + if (isExtensionSupported(mCameraId, extensionType, mCharacteristicsMapNative)) { ret.add(extensionType); } } @@ -597,18 +605,18 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); return extender.isPostviewAvailable(); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); return extenders.second.isPostviewAvailable(); } } catch (RemoteException e) { @@ -655,7 +663,7 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } @@ -664,7 +672,7 @@ public final class CameraExtensionCharacteristics { sz.width = captureSize.getWidth(); sz.height = captureSize.getHeight(); - StreamConfigurationMap streamMap = mChars.get( + StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported()) { @@ -676,13 +684,13 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported format: " + format); } IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); return generateSupportedSizes(extender.getSupportedPostviewResolutions( sz), format, streamMap); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if ((extenders.second.getCaptureProcessor() == null) || !isPostviewAvailable(extension)) { // Extensions that don't implement any capture processor @@ -754,22 +762,23 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } - StreamConfigurationMap streamMap = mChars.get( + StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); return generateSupportedSizes( extender.getSupportedPreviewOutputResolutions(mCameraId), ImageFormat.PRIVATE, streamMap); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.first.init(mCameraId, mChars.getNativeMetadata()); + extenders.first.init(mCameraId, + mCharacteristicsMapNative.get(mCameraId)); return generateSupportedSizes(extenders.first.getSupportedResolutions(), ImageFormat.PRIVATE, streamMap); } @@ -811,11 +820,11 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } - StreamConfigurationMap streamMap = mChars.get( + StreamConfigurationMap streamMap = mCharacteristicsMap.get(mCameraId).get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (areAdvancedExtensionsSupported()) { switch(format) { @@ -826,14 +835,14 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported format: " + format); } IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); return generateSupportedSizes(extender.getSupportedCaptureOutputResolutions( mCameraId), format, streamMap); } else { if (format == ImageFormat.YUV_420_888) { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if (extenders.second.getCaptureProcessor() == null) { // Extensions that don't implement any capture processor are limited to // JPEG only! @@ -844,7 +853,7 @@ public final class CameraExtensionCharacteristics { } else if (format == ImageFormat.JPEG) { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if (extenders.second.getCaptureProcessor() != null) { // The framework will perform the additional encoding pass on the // processed YUV_420 buffers. @@ -900,7 +909,7 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } @@ -910,7 +919,7 @@ public final class CameraExtensionCharacteristics { sz.height = captureOutputSize.getHeight(); if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); LatencyRange latencyRange = extender.getEstimatedCaptureLatencyRange(mCameraId, sz, format); if (latencyRange != null) { @@ -919,7 +928,7 @@ public final class CameraExtensionCharacteristics { } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); if ((format == ImageFormat.YUV_420_888) && (extenders.second.getCaptureProcessor() == null) ){ // Extensions that don't implement any capture processor are limited to @@ -965,18 +974,18 @@ public final class CameraExtensionCharacteristics { } try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); return extender.isCaptureProcessProgressAvailable(); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); return extenders.second.isCaptureProcessProgressAvailable(); } } catch (RemoteException e) { @@ -1012,20 +1021,20 @@ public final class CameraExtensionCharacteristics { HashSet<CaptureRequest.Key> ret = new HashSet<>(); try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } CameraMetadataNative captureRequestMeta = null; if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); captureRequestMeta = extender.getAvailableCaptureRequestKeys(mCameraId); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mChars.getNativeMetadata()); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys(); extenders.second.onDeInit(); } @@ -1090,20 +1099,20 @@ public final class CameraExtensionCharacteristics { HashSet<CaptureResult.Key> ret = new HashSet<>(); try { - if (!isExtensionSupported(mCameraId, extension, mChars)) { + if (!isExtensionSupported(mCameraId, extension, mCharacteristicsMapNative)) { throw new IllegalArgumentException("Unsupported extension"); } CameraMetadataNative captureResultMeta = null; if (areAdvancedExtensionsSupported()) { IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension); - extender.init(mCameraId); + extender.init(mCameraId, mCharacteristicsMapNative); captureResultMeta = extender.getAvailableCaptureResultKeys(mCameraId); } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mChars.getNativeMetadata()); - extenders.second.init(mCameraId, mChars.getNativeMetadata()); + extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureResultMeta = extenders.second.getAvailableCaptureResultKeys(); extenders.second.onDeInit(); } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 73dd50945289..c6fc69efb85c 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -694,7 +694,10 @@ public final class CameraManager { public CameraExtensionCharacteristics getCameraExtensionCharacteristics( @NonNull String cameraId) throws CameraAccessException { CameraCharacteristics chars = getCameraCharacteristics(cameraId); - return new CameraExtensionCharacteristics(mContext, cameraId, chars); + Map<String, CameraCharacteristics> characteristicsMap = getPhysicalIdToCharsMap(chars); + characteristicsMap.put(cameraId, chars); + + return new CameraExtensionCharacteristics(mContext, cameraId, characteristicsMap); } private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap( diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl index c9b7ea139cb9..101442f28c29 100644 --- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl @@ -15,6 +15,8 @@ */ package android.hardware.camera2.extension; +import android.hardware.camera2.impl.CameraMetadataNative; + import android.hardware.camera2.extension.ISessionProcessorImpl; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.Size; @@ -24,8 +26,8 @@ import android.hardware.camera2.impl.CameraMetadataNative; /** @hide */ interface IAdvancedExtenderImpl { - boolean isExtensionAvailable(in String cameraId); - void init(in String cameraId); + boolean isExtensionAvailable(in String cameraId, in Map<String, CameraMetadataNative> charsMap); + void init(in String cameraId, in Map<String, CameraMetadataNative> charsMap); LatencyRange getEstimatedCaptureLatencyRange(in String cameraId, in Size outputSize, int format); @nullable List<SizeList> getSupportedPreviewOutputResolutions(in String cameraId); diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl index 2af1df9e1a7e..13b93a8c5e92 100644 --- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl @@ -15,6 +15,8 @@ */ package android.hardware.camera2.extension; +import android.hardware.camera2.impl.CameraMetadataNative; + import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.extension.CameraSessionConfig; import android.hardware.camera2.extension.ICaptureCallback; @@ -26,7 +28,8 @@ import android.hardware.camera2.extension.OutputSurface; /** @hide */ interface ISessionProcessorImpl { - CameraSessionConfig initSession(in String cameraId, in OutputSurface previewSurface, + CameraSessionConfig initSession(in String cameraId, + in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface, in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface); void deInitSession(); void onCaptureSessionStart(IRequestProcessorImpl requestProcessor); diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index cfade5532df7..e787779d0f57 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -29,7 +29,6 @@ import android.hardware.camera2.CameraCharacteristics; 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; @@ -78,6 +77,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private final Executor mExecutor; private final CameraDevice mCameraDevice; + private final Map<String, CameraMetadataNative> mCharacteristicsMap; private final long mExtensionClientId; private final Handler mHandler; private final HandlerThread mHandlerThread; @@ -109,6 +109,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @RequiresPermission(android.Manifest.permission.CAMERA) public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession( @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice, + @NonNull Map<String, CameraCharacteristics> characteristicsMap, @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { long clientId = CameraExtensionCharacteristics.registerClient(ctx); @@ -117,13 +118,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } String cameraId = cameraDevice.getId(); - CameraManager manager = ctx.getSystemService(CameraManager.class); - CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId); CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx, - cameraId, chars); + cameraId, characteristicsMap); + Map<String, CameraMetadataNative> characteristicsMapNative = + CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap); if (!CameraExtensionCharacteristics.isExtensionSupported(cameraDevice.getId(), - config.getExtension(), chars)) { + config.getExtension(), characteristicsMapNative)) { throw new UnsupportedOperationException("Unsupported extension type: " + config.getExtension()); } @@ -198,11 +199,12 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension( config.getExtension()); - extender.init(cameraId); + extender.init(cameraId, characteristicsMapNative); CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId, - extender, cameraDevice, repeatingRequestSurface, burstCaptureSurface, - postviewSurface, config.getStateCallback(), config.getExecutor(), sessionId); + extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface, + burstCaptureSurface, postviewSurface, config.getStateCallback(), + config.getExecutor(), sessionId); ret.initialize(); return ret; @@ -210,14 +212,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private CameraAdvancedExtensionSessionImpl(long extensionClientId, @NonNull IAdvancedExtenderImpl extender, - @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice, + @NonNull CameraDeviceImpl cameraDevice, + Map<String, CameraMetadataNative> characteristicsMap, @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @Nullable Surface postviewSurface, - @NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor, + @NonNull StateCallback callback, @NonNull Executor executor, int sessionId) { mExtensionClientId = extensionClientId; mAdvancedExtender = extender; mCameraDevice = cameraDevice; + mCharacteristicsMap = characteristicsMap; mCallbacks = callback; mExecutor = executor; mClientRepeatingRequestSurface = repeatingRequestSurface; @@ -247,7 +251,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mSessionProcessor = mAdvancedExtender.getSessionProcessor(); CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(), - previewSurface, captureSurface, postviewSurface); + mCharacteristicsMap, previewSurface, captureSurface, postviewSurface); List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs; ArrayList<OutputConfiguration> outputList = new ArrayList<>(); for (CameraOutputConfig output : outputConfigs) { @@ -542,7 +546,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes if (mExtensionClientId >= 0) { CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized) { + if (mInitialized || (mCaptureSession != null)) { notifyClose = true; CameraExtensionCharacteristics.releaseSession(); } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 693b5e085d8a..bf4a20dfcae0 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -2517,14 +2517,19 @@ public class CameraDeviceImpl extends CameraDevice @Override public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { + HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>( + mPhysicalIdsToChars); + characteristicsMap.put(mCameraId, mCharacteristics); try { if (CameraExtensionCharacteristics.areAdvancedExtensionsSupported()) { mCurrentAdvancedExtensionSession = CameraAdvancedExtensionSessionImpl.createCameraAdvancedExtensionSession( - this, mContext, extensionConfiguration, mNextSessionId++); + this, characteristicsMap, mContext, extensionConfiguration, + mNextSessionId++); } else { mCurrentExtensionSession = CameraExtensionSessionImpl.createCameraExtensionSession( - this, mContext, extensionConfiguration, mNextSessionId++); + this, characteristicsMap, mContext, extensionConfiguration, + mNextSessionId++); } } catch (RemoteException e) { throw new CameraAccessException(CameraAccessException.CAMERA_ERROR); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 9c878c78855b..8e7c7e0cfca8 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -129,6 +129,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @RequiresPermission(android.Manifest.permission.CAMERA) public static CameraExtensionSessionImpl createCameraExtensionSession( @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice, + @NonNull Map<String, CameraCharacteristics> characteristicsMap, @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId) @@ -139,13 +140,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } String cameraId = cameraDevice.getId(); - CameraManager manager = ctx.getSystemService(CameraManager.class); - CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId); CameraExtensionCharacteristics extensionChars = new CameraExtensionCharacteristics(ctx, - cameraId, chars); + cameraId, characteristicsMap); if (!CameraExtensionCharacteristics.isExtensionSupported(cameraDevice.getId(), - config.getExtension(), chars)) { + config.getExtension(), + CameraExtensionUtils.getCharacteristicsMapNative(characteristicsMap))) { throw new UnsupportedOperationException("Unsupported extension type: " + config.getExtension()); } @@ -222,10 +222,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } } - extenders.first.init(cameraId, chars.getNativeMetadata()); - extenders.first.onInit(cameraId, chars.getNativeMetadata()); - extenders.second.init(cameraId, chars.getNativeMetadata()); - extenders.second.onInit(cameraId, chars.getNativeMetadata()); + extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); CameraExtensionSessionImpl session = new CameraExtensionSessionImpl( extenders.second, @@ -840,7 +840,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { if (mExtensionClientId >= 0) { CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized) { + if (mInitialized || (mCaptureSession != null)) { notifyClose = true; CameraExtensionCharacteristics.releaseSession(); } diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java index 08111c5f3874..f4fc472accbe 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.hardware.HardwareBuffer; +import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraExtensionCharacteristics; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; @@ -34,6 +35,7 @@ import android.view.Surface; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; @@ -169,4 +171,13 @@ public final class CameraExtensionUtils { return null; } + + public static Map<String, CameraMetadataNative> getCharacteristicsMapNative( + Map<String, CameraCharacteristics> charsMap) { + HashMap<String, CameraMetadataNative> ret = new HashMap<>(); + for (Map.Entry<String, CameraCharacteristics> entry : charsMap.entrySet()) { + ret.put(entry.getKey(), entry.getValue().getNativeMetadata()); + } + return ret; + } } diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java index d54fcd2ed5b3..b002ca21e8e3 100644 --- a/core/java/android/nfc/NfcAntennaInfo.java +++ b/core/java/android/nfc/NfcAntennaInfo.java @@ -85,8 +85,8 @@ public final class NfcAntennaInfo implements Parcelable { this.mDeviceHeight = in.readInt(); this.mDeviceFoldable = in.readByte() != 0; this.mAvailableNfcAntennas = new ArrayList<>(); - in.readParcelableList(this.mAvailableNfcAntennas, - AvailableNfcAntenna.class.getClassLoader()); + in.readTypedList(this.mAvailableNfcAntennas, + AvailableNfcAntenna.CREATOR); } public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 73c29d4058cd..867dafe7f552 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5453,6 +5453,14 @@ public final class Settings { public static final String SHOW_TOUCHES = "show_touches"; /** + * Show key presses and other events dispatched to focused windows on the screen. + * 0 = no + * 1 = yes + * @hide + */ + public static final String SHOW_KEY_PRESSES = "show_key_presses"; + + /** * Log raw orientation data from * {@link com.android.server.policy.WindowOrientationListener} for use with the * orientationplot.py tool. @@ -5842,6 +5850,7 @@ public final class Settings { PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE); PRIVATE_SETTINGS.add(POINTER_LOCATION); PRIVATE_SETTINGS.add(SHOW_TOUCHES); + PRIVATE_SETTINGS.add(SHOW_KEY_PRESSES); PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG); PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED); PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 90e8cedab622..4b761c105803 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -389,7 +389,7 @@ public class VoiceInteractionService extends Service { // It's still guaranteed to have been stopped. // This helps with cases where the voice interaction implementation is changed // by the user. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(true); } /** @@ -715,7 +715,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (detector.isUsingSandboxedDetectionService() @@ -878,7 +878,7 @@ public class VoiceInteractionService extends Service { synchronized (mLock) { if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) { // Allow only one concurrent recognition via the APIs. - safelyShutdownAllHotwordDetectors(); + safelyShutdownAllHotwordDetectors(false); } else { for (HotwordDetector detector : mActiveDetectors) { if (!detector.isUsingSandboxedDetectionService()) { @@ -1062,11 +1062,14 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null; } - private void safelyShutdownAllHotwordDetectors() { + private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) { synchronized (mLock) { mActiveDetectors.forEach(detector -> { try { - detector.destroy(); + if (detector != mActiveVisualQueryDetector.getInitializationDelegate() + || shouldShutDownVisualQueryDetector) { + detector.destroy(); + } } catch (Exception ex) { Log.i(TAG, "exception destroying HotwordDetector", ex); } @@ -1116,6 +1119,8 @@ public class VoiceInteractionService extends Service { pw.println(); }); } + pw.println("Available Model Enrollment Applications:"); + pw.println(" " + mKeyphraseEnrollmentInfo); } } } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 21b14f4fe7db..428a07f1ca7b 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -15,12 +15,17 @@ */ package android.speech.tts; +import static android.content.Context.DEVICE_ID_DEFAULT; +import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RawRes; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.companion.virtual.VirtualDevice; +import android.companion.virtual.VirtualDeviceManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentResolver; @@ -791,9 +796,48 @@ public class TextToSpeech { mIsSystem = isSystem; + addDeviceSpecificSessionIdToParams(mContext, mParams); initTts(); } + /** + * Add {@link VirtualDevice} specific playback audio session associated with context to + * parameters {@link Bundle} if applicable. + * + * @param context - {@link Context} context instance to extract the device specific audio + * session id from. + * @param params - {@link Bundle} to add the device specific audio session id to. + */ + private static void addDeviceSpecificSessionIdToParams( + @NonNull Context context, @NonNull Bundle params) { + int audioSessionId = getDeviceSpecificPlaybackSessionId(context); + if (audioSessionId != AUDIO_SESSION_ID_GENERATE) { + params.putInt(Engine.KEY_PARAM_SESSION_ID, audioSessionId); + } + } + + /** + * Helper method to fetch {@link VirtualDevice} specific playback audio session id for given + * {@link Context} instance. + * + * @param context - {@link Context} to fetch the audio sesion id for. + * @return audio session id corresponding to {@link VirtualDevice} in case the context is + * associated with {@link VirtualDevice} configured with specific audio session id, + * {@link AudioManager#AUDIO_SESSION_ID_GENERATE} otherwise. + * @see android.companion.virtual.VirtualDeviceManager#getAudioPlaybackSessionId(int) + */ + private static int getDeviceSpecificPlaybackSessionId(@NonNull Context context) { + int deviceId = context.getDeviceId(); + if (deviceId == DEVICE_ID_DEFAULT) { + return AUDIO_SESSION_ID_GENERATE; + } + VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class); + if (vdm == null) { + return AUDIO_SESSION_ID_GENERATE; + } + return vdm.getAudioPlaybackSessionId(deviceId); + } + private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method, boolean onlyEstablishedConnection) { return runAction(action, errorResult, method, false, onlyEstablishedConnection); diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index d8fa533dbef7..512f4f2b5d22 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -647,7 +647,9 @@ public final class DisplayInfo implements Parcelable { if (refreshRateOverride > 0) { return refreshRateOverride; } - + if (supportedModes.length == 0) { + return 0; + } return getMode().getRefreshRate(); } @@ -665,7 +667,9 @@ public final class DisplayInfo implements Parcelable { return supportedModes[i]; } } - throw new IllegalStateException("Unable to locate mode " + id); + throw new IllegalStateException( + "Unable to locate mode id=" + id + ",supportedModes=" + Arrays.toString( + supportedModes)); } /** diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index dd4f9644da96..ab58306ba5ab 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -85,7 +85,21 @@ public class HandwritingInitiator { * to {@link #findBestCandidateView(float, float)}. */ @Nullable - private View mCachedHoverTarget = null; + private WeakReference<View> mCachedHoverTarget = null; + + /** + * Whether to show the hover icon for the current connected view. + * Hover icon should be hidden for the current connected view after handwriting is initiated + * for it until one of the following events happens: + * a) user performs a click or long click. In other words, if it receives a series of motion + * events that don't trigger handwriting, show hover icon again. + * b) the stylus hovers on another editor that supports handwriting (or a handwriting delegate). + * c) the current connected editor lost focus. + * + * If the stylus is hovering on an unconnected editor that supports handwriting, we always show + * the hover icon. + */ + private boolean mShowHoverIconForConnectedView = true; @VisibleForTesting public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration, @@ -142,6 +156,12 @@ public class HandwritingInitiator { // check whether the stylus we are tracking goes up. if (mState != null) { mState.mShouldInitHandwriting = false; + if (!mState.mHasInitiatedHandwriting + && !mState.mHasPreparedHandwritingDelegation) { + // The user just did a click, long click or another stylus gesture, + // show hover icon again for the connected view. + mShowHoverIconForConnectedView = true; + } } return false; case MotionEvent.ACTION_MOVE: @@ -214,7 +234,11 @@ public class HandwritingInitiator { */ public void onDelegateViewFocused(@NonNull View view) { if (view == getConnectedView()) { - tryAcceptStylusHandwritingDelegation(view); + if (tryAcceptStylusHandwritingDelegation(view)) { + // A handwriting delegate view is accepted and handwriting starts; hide the + // hover icon. + mShowHoverIconForConnectedView = false; + } } } @@ -237,7 +261,12 @@ public class HandwritingInitiator { } else { mConnectedView = new WeakReference<>(view); mConnectionCount = 1; + // A new view just gain focus. By default, we should show hover icon for it. + mShowHoverIconForConnectedView = true; if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) { + // A handwriting delegate view is accepted and handwriting starts; hide the + // hover icon. + mShowHoverIconForConnectedView = false; return; } if (mState != null && mState.mShouldInitHandwriting) { @@ -306,6 +335,7 @@ public class HandwritingInitiator { mImm.startStylusHandwriting(view); mState.mHasInitiatedHandwriting = true; mState.mShouldInitHandwriting = false; + mShowHoverIconForConnectedView = false; if (view instanceof TextView) { ((TextView) view).hideHint(); } @@ -361,15 +391,35 @@ public class HandwritingInitiator { * handwrite-able area. */ public PointerIcon onResolvePointerIcon(Context context, MotionEvent event) { - if (shouldShowHandwritingPointerIcon(event)) { + final View hoverView = findHoverView(event); + if (hoverView == null) { + return null; + } + + if (mShowHoverIconForConnectedView) { + return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING); + } + + if (hoverView != getConnectedView()) { + // The stylus is hovering on another view that supports handwriting. We should show + // hover icon. Also reset the mShowHoverIconForConnectedView so that hover + // icon is displayed again next time when the stylus hovers on connected view. + mShowHoverIconForConnectedView = true; return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING); } return null; } - private boolean shouldShowHandwritingPointerIcon(MotionEvent event) { + private View getCachedHoverTarget() { + if (mCachedHoverTarget == null) { + return null; + } + return mCachedHoverTarget.get(); + } + + private View findHoverView(MotionEvent event) { if (!event.isStylusPointer() || !event.isHoverEvent()) { - return false; + return null; } if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER @@ -377,24 +427,25 @@ public class HandwritingInitiator { final float hoverX = event.getX(event.getActionIndex()); final float hoverY = event.getY(event.getActionIndex()); - if (mCachedHoverTarget != null) { - final Rect handwritingArea = getViewHandwritingArea(mCachedHoverTarget); - if (isInHandwritingArea(handwritingArea, hoverX, hoverY, mCachedHoverTarget) - && shouldTriggerStylusHandwritingForView(mCachedHoverTarget)) { - return true; + final View cachedHoverTarget = getCachedHoverTarget(); + if (cachedHoverTarget != null) { + final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget); + if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget) + && shouldTriggerStylusHandwritingForView(cachedHoverTarget)) { + return cachedHoverTarget; } } final View candidateView = findBestCandidateView(hoverX, hoverY); if (candidateView != null) { - mCachedHoverTarget = candidateView; - return true; + mCachedHoverTarget = new WeakReference<>(candidateView); + return candidateView; } } mCachedHoverTarget = null; - return false; + return null; } private static void requestFocusWithoutReveal(View view) { diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 39029896331c..1af8ca2efe11 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -4167,6 +4167,40 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** + * Get the x coordinate of the location where the pointer should be dispatched. + * + * This is required because a mouse event, such as from a touchpad, may contain multiple + * pointers that should all be dispatched to the cursor position. + * @hide + */ + public float getXDispatchLocation(int pointerIndex) { + if (isFromSource(InputDevice.SOURCE_MOUSE)) { + final float xCursorPosition = getXCursorPosition(); + if (xCursorPosition != INVALID_CURSOR_POSITION) { + return xCursorPosition; + } + } + return getX(pointerIndex); + } + + /** + * Get the y coordinate of the location where the pointer should be dispatched. + * + * This is required because a mouse event, such as from a touchpad, may contain multiple + * pointers that should all be dispatched to the cursor position. + * @hide + */ + public float getYDispatchLocation(int pointerIndex) { + if (isFromSource(InputDevice.SOURCE_MOUSE)) { + final float yCursorPosition = getYCursorPosition(); + if (yCursorPosition != INVALID_CURSOR_POSITION) { + return yCursorPosition; + } + } + return getY(pointerIndex); + } + + /** * Transfer object for pointer coordinates. * * Objects of this type can be used to specify the pointer coordinates when diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 99deac4c8cf4..62fdfaea1303 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -280,7 +280,7 @@ public final class SurfaceControl implements Parcelable { private static native int nativeGetLayerId(long nativeObject); private static native void nativeAddTransactionCommittedListener(long nativeObject, TransactionCommittedListener listener); - private static native void nativeSanitize(long transactionObject); + private static native void nativeSanitize(long transactionObject, int pid, int uid); private static native void nativeSetDestinationFrame(long transactionObj, long nativeObject, int l, int t, int r, int b); private static native void nativeSetDefaultApplyToken(IBinder token); @@ -3960,8 +3960,8 @@ public final class SurfaceControl implements Parcelable { /** * @hide */ - public void sanitize() { - nativeSanitize(mNativeObject); + public void sanitize(int pid, int uid) { + nativeSanitize(mNativeObject, pid, uid); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 705a2ce05bc6..6af160c04b4b 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9845,10 +9845,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its - * children) will be always be considered not important; for example, when the user explicitly - * makes an autofill request, all views are considered important. See - * {@link #isImportantForAutofill()} for more details about how the View's importance for - * autofill is used. + * children) will not be used for autofill purpose; for example, when the user explicitly + * makes an autofill request, all views are included in the ViewStructure, and starting in + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} the system uses other factors along + * with importance to determine the autofill behavior. See {@link #isImportantForAutofill()} + * for more details about how the View's importance for autofill is used. * * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES}, * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, @@ -9894,21 +9895,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>otherwise, it returns {@code false}. * </ol> * - * <p>When a view is considered important for autofill: - * <ul> - * <li>The view might automatically trigger an autofill request when focused on. - * <li>The contents of the view are included in the {@link ViewStructure} used in an autofill - * request. - * </ul> - * - * <p>On the other hand, when a view is considered not important for autofill: - * <ul> - * <li>The view never automatically triggers autofill requests, but it can trigger a manual - * request through {@link AutofillManager#requestAutofill(View)}. - * <li>The contents of the view are not included in the {@link ViewStructure} used in an - * autofill request, unless the request has the - * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. - * </ul> + * <p> The behavior of importances depends on Android version: + * <ol> + * <li>For {@link android.os.Build.VERSION_CODES#TIRAMISU} and below: + * <ol> + * <li>When a view is considered important for autofill: + * <ol> + * <li>The view might automatically trigger an autofill request when focused on. + * <li>The contents of the view are included in the {@link ViewStructure} used in an + * autofill request. + * </ol> + * <li>On the other hand, when a view is considered not important for autofill: + * <ol> + * <li>The view never automatically triggers autofill requests, but it can trigger a + * manual request through {@link AutofillManager#requestAutofill(View)}. + * <li>The contents of the view are not included in the {@link ViewStructure} used in + * an autofill request, unless the request has the + * {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag. + * </ol> + * </ol> + * <li>For {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above: + * <ol> + * <li>The system uses importance, along with other view properties and other optimization + * factors, to determine if a view should trigger autofill on focus. + * <li>The contents of {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES}, {@link #IMPORTANT_FOR_AUTOFILL_NO}, + * {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, and + * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} views will be included in the + * {@link ViewStructure} used in an autofill request. + * </ol> + * </ol> * * @return whether the view is considered important for autofill. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f5e4da86bfea..d4578475e9c3 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2040,8 +2040,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); + final float x = event.getXDispatchLocation(pointerIndex); + final float y = event.getYDispatchLocation(pointerIndex); if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) { return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW); } @@ -2125,8 +2125,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager HoverTarget firstOldHoverTarget = mFirstHoverTarget; mFirstHoverTarget = null; if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) { - final float x = event.getX(); - final float y = event.getY(); + final float x = event.getXDispatchLocation(0); + final float y = event.getYDispatchLocation(0); final int childrenCount = mChildrenCount; if (childrenCount != 0) { final ArrayList<View> preorderedList = buildOrderedChildList(); @@ -2347,8 +2347,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Check what the child under the pointer says about the tooltip. final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final float x = event.getX(); - final float y = event.getY(); + final float x = event.getXDispatchLocation(0); + final float y = event.getYDispatchLocation(0); final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null @@ -2443,8 +2443,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override protected boolean pointInHoveredChild(MotionEvent event) { if (mFirstHoverTarget != null) { - return isTransformedTouchPointInView(event.getX(), event.getY(), - mFirstHoverTarget.child, null); + return isTransformedTouchPointInView(event.getXDispatchLocation(0), + event.getYDispatchLocation(0), mFirstHoverTarget.child, null); } return false; } @@ -2513,8 +2513,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public boolean onInterceptHoverEvent(MotionEvent event) { if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { final int action = event.getAction(); - final float x = event.getX(); - final float y = event.getY(); + final float x = event.getXDispatchLocation(0); + final float y = event.getYDispatchLocation(0); if ((action == MotionEvent.ACTION_HOVER_MOVE || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) { return true; @@ -2535,8 +2535,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Send the event to the child under the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { - final float x = event.getX(); - final float y = event.getY(); + final float x = event.getXDispatchLocation(0); + final float y = event.getXDispatchLocation(0); final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null @@ -2700,10 +2700,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { - final float x = - isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex); - final float y = - isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex); + final float x = ev.getXDispatchLocation(actionIndex); + final float y = ev.getYDispatchLocation(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); @@ -2757,8 +2755,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } else { mLastTouchDownIndex = childIndex; } - mLastTouchDownX = ev.getX(); - mLastTouchDownY = ev.getY(); + mLastTouchDownX = x; + mLastTouchDownY = y; newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; @@ -3287,7 +3285,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (ev.isFromSource(InputDevice.SOURCE_MOUSE) && ev.getAction() == MotionEvent.ACTION_DOWN && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) - && isOnScrollbarThumb(ev.getX(), ev.getY())) { + && isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) { return true; } return false; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3208b6281110..8d74e99c56ba 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3780,6 +3780,16 @@ public final class ViewRootImpl implements ViewParent, createSyncIfNeeded(); notifyDrawStarted(isInWMSRequestedSync()); mDrewOnceForSync = true; + + // If the active SSG is also requesting to sync a buffer, the following needs to happen + // 1. Ensure we keep track of the number of active syncs to know when to disable RT + // RT animations that conflict with syncing a buffer. + // 2. Add a safeguard SSG to prevent multiple SSG that sync buffers from being submitted + // out of order. + if (mActiveSurfaceSyncGroup != null && mSyncBuffer) { + updateSyncInProgressCount(mActiveSurfaceSyncGroup); + safeguardOverlappingSyncs(mActiveSurfaceSyncGroup); + } } if (!isViewVisible) { @@ -3844,14 +3854,11 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_MERGED; reportDrawFinished(t, seqId); }); - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "create WMS Sync group=" + mWmsRequestSyncGroup.getName()); if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); } mWmsRequestSyncGroup.add(this, null /* runnable */); - Trace.traceEnd(Trace.TRACE_TAG_VIEW); } private void notifyContentCaptureEvents() { @@ -4512,6 +4519,9 @@ public final class ViewRootImpl implements ViewParent, Log.d(mTag, "reportDrawFinished"); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId); + } try { mWindowSession.finishDrawing(mWindow, t, seqId); } catch (RemoteException e) { @@ -11395,7 +11405,7 @@ public final class ViewRootImpl implements ViewParent, * ensure the latter SSG always waits for the former SSG's transaction to get to SF. */ private void safeguardOverlappingSyncs(SurfaceSyncGroup activeSurfaceSyncGroup) { - SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("VRI-Safeguard"); + SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("Safeguard-" + mTag); // Always disable timeout on the safeguard sync safeguardSsg.toggleTimeout(false /* enable */); synchronized (mPreviousSyncSafeguardLock) { @@ -11454,8 +11464,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.post(runnable); } }); - safeguardOverlappingSyncs(mActiveSurfaceSyncGroup); - updateSyncInProgressCount(mActiveSurfaceSyncGroup); newSyncGroup = true; } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 537822e17e08..05aff6425a67 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -622,15 +622,11 @@ public class BaseInputConnection implements InputConnection { if (b < 0) { b = 0; } - - if (b + length > content.length()) { - length = content.length() - b; - } - + int end = (int) Math.min((long) b + length, content.length()); if ((flags&GET_TEXT_WITH_STYLES) != 0) { - return content.subSequence(b, b + length); + return content.subSequence(b, end); } - return TextUtils.substring(content, b, b + length); + return TextUtils.substring(content, b, end); } /** @@ -666,13 +662,9 @@ public class BaseInputConnection implements InputConnection { selEnd = tmp; } - int contentLength = content.length(); - int startPos = selStart - beforeLength; - int endPos = selEnd + afterLength; - // Guards the start and end pos within range [0, contentLength]. - startPos = Math.max(0, startPos); - endPos = Math.min(contentLength, endPos); + int startPos = Math.max(0, selStart - beforeLength); + int endPos = (int) Math.min((long) selEnd + afterLength, content.length()); CharSequence surroundingText; if ((flags & GET_TEXT_WITH_STYLES) != 0) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6c84f35f06f5..db7d48471d9d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6228,7 +6228,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) { - Preconditions.checkArgumentNonnegative((int) lineHeight); + Preconditions.checkArgumentNonNegative(lineHeight, + "Expecting non-negative lineHeight while the input is " + lineHeight); final int fontHeight = getPaint().getFontMetricsInt(null); // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 18405676dbd8..dfdff9ef97ce 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -53,7 +53,6 @@ import java.util.function.Supplier; * This will also allow synchronization of surfaces across multiple processes. The caller can add * SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process * and this clas will ensure all the surfaces are ready before applying everything together. - * </p> * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a> * </p> */ @@ -136,6 +135,7 @@ public final class SurfaceSyncGroup { @GuardedBy("mLock") private boolean mTimeoutDisabled; + private final String mTrackName; private static boolean isLocalBinder(IBinder binder) { return !(binder instanceof BinderProxy); @@ -192,6 +192,7 @@ public final class SurfaceSyncGroup { } mName = name + "#" + sCounter.getAndIncrement(); + mTrackName = "SurfaceSyncGroup " + name; mTransactionReadyConsumer = (transaction) -> { if (DEBUG && transaction != null) { @@ -199,9 +200,10 @@ public final class SurfaceSyncGroup { + mName); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - "Final TransactionCallback with " + transaction + " for " + mName); + Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, + "Final TransactionCallback with " + transaction); } + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); transactionReadyConsumer.accept(transaction); synchronized (mLock) { // If there's a registered listener with WMS, that means we aren't actually complete @@ -213,7 +215,7 @@ public final class SurfaceSyncGroup { }; if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, mName, hashCode()); } if (DEBUG) { @@ -257,7 +259,7 @@ public final class SurfaceSyncGroup { Log.d(TAG, "markSyncReady " + mName); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName); + Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady"); } synchronized (mLock) { if (mHasWMSync) { @@ -269,9 +271,6 @@ public final class SurfaceSyncGroup { mSyncReady = true; checkIfSyncIsComplete(); } - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); - } } /** @@ -399,14 +398,14 @@ public final class SurfaceSyncGroup { public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge, @Nullable Runnable runnable) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "addToSync token=" + mToken.hashCode() + " parent=" + mName); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, + "addToSync token=" + mToken.hashCode(), hashCode()); } synchronized (mLock) { if (mSyncReady) { Log.w(TAG, "Trying to add to sync when already marked as ready " + mName); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return false; } @@ -419,7 +418,7 @@ public final class SurfaceSyncGroup { if (isLocalBinder(surfaceSyncGroup.asBinder())) { boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return didAddLocalSync; } @@ -447,7 +446,7 @@ public final class SurfaceSyncGroup { mSurfaceSyncGroupCompletedListener)) { mSurfaceSyncGroupCompletedListener = null; if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return false; } @@ -459,13 +458,13 @@ public final class SurfaceSyncGroup { surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge); } catch (RemoteException e) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return false; } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return true; } @@ -510,15 +509,15 @@ public final class SurfaceSyncGroup { + ". Setting up Sync in WindowManager."); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "addSyncToWm=" + token.hashCode() + " group=" + mName); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, + "addSyncToWm=" + token.hashCode(), hashCode()); } AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult(); if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token, parentSyncGroupMerge, surfaceSyncGroupCompletedListener, addToSyncGroupResult)) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return false; } @@ -527,12 +526,12 @@ public final class SurfaceSyncGroup { addToSyncGroupResult.mTransactionReadyCallback); } catch (RemoteException e) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return false; } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return true; } @@ -550,8 +549,8 @@ public final class SurfaceSyncGroup { } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, + "addLocalSync=" + childSurfaceSyncGroup.mName, hashCode()); } ITransactionReadyCallback callback = createTransactionReadyCallback(parentSyncGroupMerge); @@ -562,7 +561,7 @@ public final class SurfaceSyncGroup { childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return true; } @@ -574,9 +573,9 @@ public final class SurfaceSyncGroup { } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, "setTransactionCallbackFromParent " + mName + " callback=" - + transactionReadyCallback.hashCode()); + + transactionReadyCallback.hashCode(), hashCode()); } // Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup. @@ -617,9 +616,9 @@ public final class SurfaceSyncGroup { mParentSyncGroup = parentSyncGroup; mTransactionReadyConsumer = (transaction) -> { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "transactionReadyCallback " + mName + " callback=" - + transactionReadyCallback.hashCode()); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, + "Invoke transactionReadyCallback=" + + transactionReadyCallback.hashCode(), hashCode()); } lastCallback.accept(null); @@ -629,7 +628,7 @@ public final class SurfaceSyncGroup { transaction.apply(); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } }; addedToSyncListener = mAddedToSyncListener; @@ -647,7 +646,7 @@ public final class SurfaceSyncGroup { addedToSyncListener.run(); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } } @@ -669,8 +668,8 @@ public final class SurfaceSyncGroup { } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady + Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, + "checkIfSyncIsComplete mSyncReady=" + mSyncReady + " mPendingSyncs=" + mPendingSyncs.size()); } @@ -715,6 +714,7 @@ public final class SurfaceSyncGroup { public void onTransactionReady(Transaction t) { synchronized (mLock) { if (t != null) { + t.sanitize(Binder.getCallingPid(), Binder.getCallingUid()); // When an older parent sync group is added due to a child syncGroup // getting added to multiple groups, we need to maintain merge order // so the older parentSyncGroup transactions are overwritten by @@ -726,9 +726,8 @@ public final class SurfaceSyncGroup { } mPendingSyncs.remove(this); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - "onTransactionReady group=" + mName + " callback=" - + hashCode()); + Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, + "onTransactionReady callback=" + hashCode()); } checkIfSyncIsComplete(); } @@ -743,8 +742,8 @@ public final class SurfaceSyncGroup { } mPendingSyncs.add(transactionReadyCallback); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instant(Trace.TRACE_TAG_VIEW, - "createTransactionReadyCallback " + mName + " mPendingSyncs=" + Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, + "createTransactionReadyCallback mPendingSyncs=" + mPendingSyncs.size() + " transactionReady=" + transactionReadyCallback.hashCode()); } @@ -764,13 +763,12 @@ public final class SurfaceSyncGroup { public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken, boolean parentSyncGroupMerge) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child=" - + mName); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, + "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode(), hashCode()); } boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode()); } return didAdd; } diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 6bc7ac6cd7d9..1f7640d97b4d 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -149,11 +149,13 @@ public final class AccessibilityStatsLogUtils { * * @param mode The activated magnification mode. * @param duration The duration in milliseconds during the magnification is activated. + * @param scale The last magnification scale for the activation */ - public static void logMagnificationUsageState(int mode, long duration) { + public static void logMagnificationUsageState(int mode, long duration, float scale) { FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED, convertToLoggingMagnificationMode(mode), - duration); + duration, + convertToLoggingMagnificationScale(scale)); } /** @@ -254,4 +256,8 @@ public final class AccessibilityStatsLogUtils { return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE; } } + + private static int convertToLoggingMagnificationScale(float scale) { + return (int) (scale * 100); + } } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 44d517ad0c32..904fb665335b 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -19,7 +19,7 @@ package com.android.internal.app; 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_OPEN_IN_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_WORK; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -227,8 +227,8 @@ public class IntentForwarderActivity extends Activity { private String getOpenInWorkMessage(CharSequence targetLabel) { return getSystemService(DevicePolicyManager.class).getResources().getString( - MINIRESOLVER_OPEN_IN_WORK, - () -> getString(R.string.miniresolver_open_in_work, targetLabel), + MINIRESOLVER_OPEN_WORK, + () -> getString(R.string.miniresolver_open_work, targetLabel), targetLabel); } diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java index 8192ffd10230..f4e9e3064f3d 100644 --- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java +++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java @@ -105,6 +105,13 @@ public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGr } if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) { + if (anchor.getParent() == null) { + // BUG(b/239050369): Check if the tracked anchor view is still attached. + Log.w(TAG, "Bug: anchor view " + anchor + " is detached after scrolling"); + resultConsumer.accept(result); // empty result + return; + } + int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++ result.scrollDelta = mScrollDelta; diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 193099baad48..424925328dfd 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -972,9 +972,9 @@ static void nativeSurfaceFlushJankData(JNIEnv* env, jclass clazz, jlong nativeOb SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(ctrl); } -static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj) { +static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj, jint pid, jint uid) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); - transaction->sanitize(); + transaction->sanitize(pid, uid); } static void nativeSetDestinationFrame(JNIEnv* env, jclass clazz, jlong transactionObj, @@ -2268,7 +2268,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*) nativeSetTrustedPresentationCallback }, {"nativeClearTrustedPresentationCallback", "(JJ)V", (void*) nativeClearTrustedPresentationCallback }, - {"nativeSanitize", "(J)V", + {"nativeSanitize", "(JII)V", (void*) nativeSanitize }, {"nativeSetDestinationFrame", "(JJIIII)V", (void*)nativeSetDestinationFrame }, diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index 7503dde440e0..48243f216015 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -68,6 +68,7 @@ message SystemSettingsProto { // orientationplot.py tool. // 0 = no, 1 = yes optional SettingProto window_orientation_listener_log = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto show_key_presses = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional DevOptions developer_options = 7; diff --git a/core/res/res/drawable-hdpi/pointer_handwriting.png b/core/res/res/drawable-hdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..6d7c59cccfc7 --- /dev/null +++ b/core/res/res/drawable-hdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-mdpi/pointer_handwriting.png b/core/res/res/drawable-mdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..b36241bec84e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-xhdpi/pointer_handwriting.png b/core/res/res/drawable-xhdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..dea1972a6216 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_handwriting.png diff --git a/core/res/res/drawable-xxhdpi/pointer_handwriting.png b/core/res/res/drawable-xxhdpi/pointer_handwriting.png Binary files differnew file mode 100644 index 000000000000..870c40206e3c --- /dev/null +++ b/core/res/res/drawable-xxhdpi/pointer_handwriting.png diff --git a/core/res/res/drawable/focus_event_pressed_key_background.xml b/core/res/res/drawable/focus_event_pressed_key_background.xml new file mode 100644 index 000000000000..e069f0bc1300 --- /dev/null +++ b/core/res/res/drawable/focus_event_pressed_key_background.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:name="focus_event_pressed_key_background" + android:shape="rectangle"> + + <!-- View background color --> + <solid + android:color="#AA000000" > + </solid> + + <!-- View border color and width --> + <stroke + android:width="2dp" + android:color="@android:color/white"> + </stroke> + + <!-- The radius makes the corners rounded --> + <corners + android:radius="8dp"> + </corners> + +</shape>
\ No newline at end of file diff --git a/core/res/res/drawable/pointer_handwriting_icon.xml b/core/res/res/drawable/pointer_handwriting_icon.xml index cdbf6938bd57..bba4e6e4c2e6 100644 --- a/core/res/res/drawable/pointer_handwriting_icon.xml +++ b/core/res/res/drawable/pointer_handwriting_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" - android:bitmap="@drawable/pointer_crosshair" - android:hotSpotX="12dp" - android:hotSpotY="12dp" />
\ No newline at end of file + android:bitmap="@drawable/pointer_handwriting" + android:hotSpotX="8.25dp" + android:hotSpotY="23.75dp" />
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 17d84021816f..984e2cae68dc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1392,9 +1392,6 @@ <!-- Number of notifications to keep in the notification service historical archive --> <integer name="config_notificationServiceArchiveSize">100</integer> - <!-- List of packages that will be able to use full screen intent in notifications by default --> - <string-array name="config_useFullScreenIntentPackages" translatable="false" /> - <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> @@ -1820,6 +1817,16 @@ specified --> <string name="default_wallpaper_component" translatable="false">@null</string> + <!-- CMF colors to default wallpaper component map, the component with color matching the device + color will be the cmf default wallpapers. The default wallpaper will be default wallpaper + component if not specified. + + E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper + <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> --> + <string-array name="cmf_default_wallpaper_component" translatable="false"> + <!-- Add packages here --> + </string-array> + <!-- By default a product has no distinct default lock wallpaper --> <item name="default_lock_wallpaper" type="drawable">@null</item> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index a49ea0bd55fb..027d4f8ab2b5 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5912,11 +5912,12 @@ <!-- Error message. This text lets the user know that their current personal apps don't support the specific content. [CHAR LIMIT=NONE] --> <string name="resolver_no_personal_apps_available">No personal apps</string> - + <!-- Dialog title. User must choose to open content in a cross-profile app or cancel. [CHAR LIMIT=NONE] --> + <string name="miniresolver_open_work">Open work <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_personal">Open personal <xliff:g id="app" example="YouTube">%s</xliff:g></string> + <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 work <xliff:g id="app" example="YouTube">%s</xliff:g></string> + <string name="miniresolver_open_in_work">Open in work <xliff:g id="app" example="YouTube">%s</xliff:g>?</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] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e3697bba3f95..73e3b417f67a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1576,6 +1576,7 @@ <java-symbol type="id" name="open_cross_profile" /> <java-symbol type="string" name="miniresolver_open_in_personal" /> <java-symbol type="string" name="miniresolver_open_in_work" /> + <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="id" name="button_open" /> @@ -2020,7 +2021,6 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> - <java-symbol type="array" name="config_useFullScreenIntentPackages" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> @@ -2110,6 +2110,7 @@ <java-symbol type="string" name="data_usage_rapid_body" /> <java-symbol type="string" name="data_usage_rapid_app_body" /> <java-symbol type="string" name="default_wallpaper_component" /> + <java-symbol type="array" name="cmf_default_wallpaper_component" /> <java-symbol type="string" name="device_storage_monitor_notification_channel" /> <java-symbol type="string" name="dlg_ok" /> <java-symbol type="string" name="dump_heap_notification" /> @@ -5119,4 +5120,6 @@ <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent" /> <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent.Light" /> <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" /> + + <java-symbol type="drawable" name="focus_event_pressed_key_background" /> </resources> diff --git a/core/tests/coretests/res/drawable-nodpi/animated_webp.webp b/core/tests/coretests/res/drawable-nodpi/animated_webp.webp Binary files differnew file mode 100644 index 000000000000..2d28dbfd380b --- /dev/null +++ b/core/tests/coretests/res/drawable-nodpi/animated_webp.webp diff --git a/core/tests/coretests/src/android/graphics/ImageDecoderTest.java b/core/tests/coretests/src/android/graphics/ImageDecoderTest.java new file mode 100644 index 000000000000..8b3e6ba283b2 --- /dev/null +++ b/core/tests/coretests/src/android/graphics/ImageDecoderTest.java @@ -0,0 +1,80 @@ +/* + * 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.graphics; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.frameworks.coretests.R; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ImageDecoderTest { + + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + + @Test + public void onDecodeHeader_png_returnsPopulatedData() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.gettysburg); + ImageDecoder.ImageInfo info = ImageDecoder.decodeHeader(src); + assertThat(info.getSize().getWidth()).isEqualTo(432); + assertThat(info.getSize().getHeight()).isEqualTo(291); + assertThat(info.getMimeType()).isEqualTo("image/png"); + assertThat(info.getColorSpace()).isNotNull(); + assertThat(info.getColorSpace().getModel()).isEqualTo(ColorSpace.Model.RGB); + assertThat(info.getColorSpace().getId()).isEqualTo(0); + assertThat(info.isAnimated()).isFalse(); + } + + @Test + public void onDecodeHeader_animatedWebP_returnsPopulatedData() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.animated_webp); + ImageDecoder.ImageInfo info = ImageDecoder.decodeHeader(src); + assertThat(info.getSize().getWidth()).isEqualTo(278); + assertThat(info.getSize().getHeight()).isEqualTo(183); + assertThat(info.getMimeType()).isEqualTo("image/webp"); + assertThat(info.getColorSpace()).isNotNull(); + assertThat(info.getColorSpace().getModel()).isEqualTo(ColorSpace.Model.RGB); + assertThat(info.getColorSpace().getId()).isEqualTo(0); + assertThat(info.isAnimated()).isTrue(); + } + + @Test(expected = IOException.class) + public void onDecodeHeader_invalidSource_throwsException() throws IOException { + ImageDecoder.Source src = ImageDecoder.createSource(new File("/this/file/does/not/exist")); + ImageDecoder.decodeHeader(src); + } + + @Test(expected = IOException.class) + public void onDecodeHeader_invalidResource_throwsException() throws IOException { + ImageDecoder.Source src = + ImageDecoder.createSource(mContext.getResources(), R.drawable.box); + ImageDecoder.decodeHeader(src); + } +} diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java new file mode 100644 index 000000000000..803d38c4208a --- /dev/null +++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DisplayInfoTest { + private static final float FLOAT_EQUAL_DELTA = 0.0001f; + + @Test + public void testDefaultDisplayInfosAreEqual() { + DisplayInfo displayInfo1 = new DisplayInfo(); + DisplayInfo displayInfo2 = new DisplayInfo(); + + assertTrue(displayInfo1.equals(displayInfo2)); + } + + @Test + public void testDefaultDisplayInfoRefreshRateIs0() { + DisplayInfo displayInfo = new DisplayInfo(); + + assertEquals(0, displayInfo.getRefreshRate(), FLOAT_EQUAL_DELTA); + } + + @Test + public void testRefreshRateOverride() { + DisplayInfo displayInfo = new DisplayInfo(); + + displayInfo.refreshRateOverride = 50; + + assertEquals(50, displayInfo.getRefreshRate(), FLOAT_EQUAL_DELTA); + + } + + @Test + public void testRefreshRateOverride_keepsDisplyInfosEqual() { + Display.Mode mode = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); + DisplayInfo displayInfo1 = new DisplayInfo(); + setSupportedMode(displayInfo1, mode); + + DisplayInfo displayInfo2 = new DisplayInfo(); + setSupportedMode(displayInfo2, mode); + displayInfo2.refreshRateOverride = 120; + + assertTrue(displayInfo1.equals(displayInfo2)); + } + + @Test + public void testRefreshRateOverride_makeDisplayInfosDifferent() { + Display.Mode mode = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); + DisplayInfo displayInfo1 = new DisplayInfo(); + setSupportedMode(displayInfo1, mode); + + DisplayInfo displayInfo2 = new DisplayInfo(); + setSupportedMode(displayInfo2, mode); + displayInfo2.refreshRateOverride = 90; + + assertFalse(displayInfo1.equals(displayInfo2)); + } + + private void setSupportedMode(DisplayInfo info, Display.Mode mode) { + info.supportedModes = new Display.Mode[]{mode}; + info.modeId = mode.getModeId(); + } + +} diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java index 506cc2d3ff97..b37c8fd8c34e 100644 --- a/core/tests/coretests/src/android/view/ViewGroupTest.java +++ b/core/tests/coretests/src/android/view/ViewGroupTest.java @@ -20,6 +20,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -87,6 +88,9 @@ public class ViewGroupTest { viewGroup.dispatchTouchEvent(event); verify(viewB).dispatchTouchEvent(event); + viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */); + verify(viewB).onResolvePointerIcon(event, 0); + event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */, MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT), 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */, @@ -95,7 +99,11 @@ public class ViewGroupTest { viewGroup.dispatchTouchEvent(event); verify(viewB).dispatchTouchEvent(event); + viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */); + verify(viewB).onResolvePointerIcon(event, 1); + verify(viewA, never()).dispatchTouchEvent(any()); + verify(viewA, never()).onResolvePointerIcon(any(), anyInt()); } /** diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java index b3886e896784..f04f603564f6 100644 --- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java @@ -549,6 +549,14 @@ public class BaseInputConnectionTest { .isEqualTo(new SurroundingText("456", 0, 3, -1))) .isTrue(); + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(Integer.MAX_VALUE, 0), "123"); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(Integer.MAX_VALUE, 0), "789"); + assertThat( + mBaseInputConnection + .getSurroundingText(Integer.MAX_VALUE, Integer.MAX_VALUE, 0) + .isEqualTo(new SurroundingText("123456789", 3, 6, -1))) + .isTrue(); + int cursorCapsMode = TextUtils.getCapsMode( "123456789", @@ -618,6 +626,45 @@ public class BaseInputConnectionTest { } @Test + public void testGetText_emptyText() { + // "" + prepareContent("", 0, 0, -1, -1); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(1, 0), ""); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(1, 0), ""); + assertThat(mBaseInputConnection.getSelectedText(0)).isNull(); + + // This falls back to default implementation in {@code InputConnection}, which always return + // -1 for offset. + assertThat( + mBaseInputConnection + .getSurroundingText(1, 1, 0) + .isEqualTo(new SurroundingText("", 0, 0, -1))) + .isTrue(); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(0, 0), ""); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(0, 0), ""); + assertThat(mBaseInputConnection.getSelectedText(0)).isNull(); + // This falls back to default implementation in {@code InputConnection}, which always return + // -1 for offset. + assertThat( + mBaseInputConnection + .getSurroundingText(0, 0, 0) + .isEqualTo(new SurroundingText("", 0, 0, -1))) + .isTrue(); + + verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(Integer.MAX_VALUE, 0), ""); + verifyContentEquals(mBaseInputConnection.getTextAfterCursor(Integer.MAX_VALUE, 0), ""); + assertThat(mBaseInputConnection.getSelectedText(0)).isNull(); + assertThat( + mBaseInputConnection + .getSurroundingText(Integer.MAX_VALUE, Integer.MAX_VALUE, 0) + .isEqualTo(new SurroundingText("", 0, 0, -1))) + .isTrue(); + } + + + @Test public void testReplaceText_toEditorWithoutSelectionAndComposing() { // before replace: "|" // after replace: "text1|" diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index c0125afef2e8..34eac35d3c0b 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -17,6 +17,7 @@ package android.view.stylus; import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static android.view.stylus.HandwritingTestUtil.createView; @@ -26,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -42,6 +44,7 @@ import android.platform.test.annotations.Presubmit; import android.view.HandwritingInitiator; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; @@ -115,6 +118,7 @@ public class HandwritingInitiatorTest { HW_BOUNDS_OFFSETS_BOTTOM_PX); mHandwritingInitiator.updateHandwritingAreasForView(mTestView1); mHandwritingInitiator.updateHandwritingAreasForView(mTestView2); + doReturn(true).when(mHandwritingInitiator).tryAcceptStylusHandwritingDelegation(any()); } @Test @@ -486,6 +490,112 @@ public class HandwritingInitiatorTest { } @Test + public void onResolvePointerIcon_withinHWArea_showPointerIcon() { + MotionEvent hoverEvent = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY()); + PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent); + assertThat(icon.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + } + + @Test + public void onResolvePointerIcon_withinExtendedHWArea_showPointerIcon() { + int x = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; + int y = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; + MotionEvent hoverEvent = createStylusHoverEvent(x, y); + + PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent); + assertThat(icon.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + } + + @Test + public void onResolvePointerIcon_afterHandwriting_hidePointerIconForConnectedView() { + // simulate the case where sTestView1 is focused. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), + /* exceedsHWSlop */ true); + // Verify that handwriting started for sTestView1. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); + + MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY()); + PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + // After handwriting is initiated for the connected view, hide the hover icon. + assertThat(icon1).isNull(); + + MotionEvent hoverEvent2 = createStylusHoverEvent(sHwArea2.centerX(), sHwArea2.centerY()); + PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent2); + // Now stylus is hovering on another editor, show the hover icon. + assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + + // After the hover icon is displayed again, it will show hover icon for the connected view + // again. + PointerIcon icon3 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + assertThat(icon3.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + } + + @Test + public void onResolvePointerIcon_afterHandwriting_hidePointerIconForDelegatorView() { + // Set mTextView2 to be the delegate of mTestView1. + mTestView2.setIsHandwritingDelegate(true); + + mTestView1.setHandwritingDelegatorCallback( + () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2)); + + injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), + /* exceedsHWSlop */ true); + // Prerequisite check, verify that handwriting started for delegateView. + verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(mTestView2); + + MotionEvent hoverEvent = createStylusHoverEvent(sHwArea2.centerX(), sHwArea2.centerY()); + PointerIcon icon = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent); + // After handwriting is initiated for the connected view, hide the hover icon. + assertThat(icon).isNull(); + } + + @Test + public void onResolvePointerIcon_showHoverIconAfterTap() { + // Simulate the case where sTestView1 is focused. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), + /* exceedsHWSlop */ true); + // Verify that handwriting started for sTestView1. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); + + MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY()); + PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + // After handwriting is initiated for the connected view, hide the hover icon. + assertThat(icon1).isNull(); + + // When exceedsHwSlop is false, it simulates a tap. + injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), + /* exceedsHWSlop */ false); + + PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + } + + @Test + public void onResolvePointerIcon_showHoverIconAfterFocusChange() { + // Simulate the case where sTestView1 is focused. + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), + /* exceedsHWSlop */ true); + // Verify that handwriting started for sTestView1. + verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); + + MotionEvent hoverEvent1 = createStylusHoverEvent(sHwArea1.centerX(), sHwArea1.centerY()); + PointerIcon icon1 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + // After handwriting is initiated for the connected view, hide the hover icon. + assertThat(icon1).isNull(); + + // Simulate that focus is switched to mTestView2 first and then switched back. + mHandwritingInitiator.onInputConnectionCreated(mTestView2); + mHandwritingInitiator.onInputConnectionCreated(mTestView1); + + PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); + // After the change of focus, hover icon shows again. + assertThat(icon2.getType()).isEqualTo(PointerIcon.TYPE_HANDWRITING); + } + + @Test public void autoHandwriting_whenDisabled_wontStartHW() { View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, true /* isStylusHandwritingAvailable */); @@ -657,6 +767,35 @@ public class HandwritingInitiatorTest { return canvas; } + /** + * Inject {@link MotionEvent}s to the {@link HandwritingInitiator}. + * @param x the x coordinate of the first {@link MotionEvent}. + * @param y the y coordinate of the first {@link MotionEvent}. + * @param exceedsHWSlop whether the injected {@link MotionEvent} movements exceed the + * handwriting slop. If true, it simulates handwriting. Otherwise, it + * simulates a tap/click, + */ + private void injectStylusEvent(HandwritingInitiator handwritingInitiator, int x, int y, + boolean exceedsHWSlop) { + MotionEvent event1 = createStylusEvent(ACTION_DOWN, x, y, 0); + + if (exceedsHWSlop) { + x += mHandwritingSlop * 2; + } else { + x += mHandwritingSlop / 2; + } + MotionEvent event2 = createStylusEvent(ACTION_MOVE, x, y, 0); + MotionEvent event3 = createStylusEvent(ACTION_UP, x, y, 0); + + handwritingInitiator.onTouchEvent(event1); + handwritingInitiator.onTouchEvent(event2); + handwritingInitiator.onTouchEvent(event3); + } + + private MotionEvent createStylusHoverEvent(int x, int y) { + return createStylusEvent(ACTION_HOVER_MOVE, x, y, /* eventTime */ 0); + } + private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS; @@ -668,6 +807,6 @@ public class HandwritingInitiatorTest { return MotionEvent.obtain(0 /* downTime */, eventTime /* eventTime */, action, 1, properties, coords, 0 /* metaState */, 0 /* buttonState */, 1 /* xPrecision */, 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, - InputDevice.SOURCE_TOUCHSCREEN, 0 /* flags */); + InputDevice.SOURCE_STYLUS, 0 /* flags */); } } diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index dd4b58eb83dc..b2da233fc21e 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -627,11 +627,19 @@ public final class ImageDecoder implements AutoCloseable { */ public static class ImageInfo { private final Size mSize; - private ImageDecoder mDecoder; + private final boolean mIsAnimated; + private final String mMimeType; + private final ColorSpace mColorSpace; - private ImageInfo(@NonNull ImageDecoder decoder) { - mSize = new Size(decoder.mWidth, decoder.mHeight); - mDecoder = decoder; + private ImageInfo( + @NonNull Size size, + boolean isAnimated, + @NonNull String mimeType, + @Nullable ColorSpace colorSpace) { + mSize = size; + mIsAnimated = isAnimated; + mMimeType = mimeType; + mColorSpace = colorSpace; } /** @@ -647,7 +655,7 @@ public final class ImageDecoder implements AutoCloseable { */ @NonNull public String getMimeType() { - return mDecoder.getMimeType(); + return mMimeType; } /** @@ -657,7 +665,7 @@ public final class ImageDecoder implements AutoCloseable { * return an {@link AnimatedImageDrawable}.</p> */ public boolean isAnimated() { - return mDecoder.mAnimated; + return mIsAnimated; } /** @@ -669,7 +677,7 @@ public final class ImageDecoder implements AutoCloseable { */ @Nullable public ColorSpace getColorSpace() { - return mDecoder.getColorSpace(); + return mColorSpace; } }; @@ -1798,12 +1806,39 @@ public final class ImageDecoder implements AutoCloseable { private void callHeaderDecoded(@Nullable OnHeaderDecodedListener listener, @NonNull Source src) { if (listener != null) { - ImageInfo info = new ImageInfo(this); - try { - listener.onHeaderDecoded(this, info, src); - } finally { - info.mDecoder = null; - } + ImageInfo info = + new ImageInfo( + new Size(mWidth, mHeight), mAnimated, getMimeType(), getColorSpace()); + listener.onHeaderDecoded(this, info, src); + } + } + + /** + * Return {@link ImageInfo} from a {@code Source}. + * + * <p>Returns the same {@link ImageInfo} object that a usual decoding process would return as + * part of {@link OnHeaderDecodedListener}. + * + * @param src representing the encoded image. + * @return ImageInfo describing the image. + * @throws IOException if {@code src} is not found, is an unsupported format, or cannot be + * decoded for any reason. + * @hide + */ + @WorkerThread + @NonNull + public static ImageInfo decodeHeader(@NonNull Source src) throws IOException { + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeHeader"); + try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) { + // We don't want to leak decoder so resolve all properties immediately. + return new ImageInfo( + new Size(decoder.mWidth, decoder.mHeight), + decoder.mAnimated, + decoder.getMimeType(), + decoder.getColorSpace()); + } finally { + // Close the ImageDecoder#decodeHeader trace. + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 9c36fc36474c..3e6457919031 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; import android.annotation.NonNull; +import android.util.ArrayMap; import android.view.Window; import libcore.util.NativeAllocationRegistry; @@ -256,6 +257,12 @@ public class RuntimeShader extends Shader { private long mNativeInstanceRuntimeShaderBuilder; /** + * For tracking GC usage. Keep a java-side reference for reachable objects to + * enable better heap tracking & tooling support + */ + private ArrayMap<String, Shader> mShaderUniforms = new ArrayMap<>(); + + /** * Creates a new RuntimeShader. * * @param shader The text of AGSL shader program to run. @@ -490,6 +497,7 @@ public class RuntimeShader extends Shader { if (shader == null) { throw new NullPointerException("The shader parameter must not be null"); } + mShaderUniforms.put(shaderName, shader); nativeUpdateShader( mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstance()); discardNativeInstance(); @@ -511,6 +519,7 @@ public class RuntimeShader extends Shader { throw new NullPointerException("The shader parameter must not be null"); } + mShaderUniforms.put(shaderName, shader); nativeUpdateShader(mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstanceWithDirectSampling()); discardNativeInstance(); diff --git a/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.xml new file mode 100644 index 000000000000..fbcf6d70f62d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <group android:scaleX="-1" android:translateX="960"> + <path + android:fillColor="?android:attr/textColorSecondary" + android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/> + </group> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml new file mode 100644 index 000000000000..d36df4ba533e --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml @@ -0,0 +1,25 @@ +<?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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="?android:attr/textColorSecondary" + android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/> +</vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml index 029d83881165..05d243dfd5f3 100644 --- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml +++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml @@ -18,11 +18,10 @@ android:width="20dp" android:height="20dp" android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> + android:viewportHeight="960"> <group android:scaleX="-1" android:translateX="960"> <path - android:fillColor="?android:attr/textColorSecondary" + android:fillColor="?android:attr/textColorSecondaryInverse" android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/> </group> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml index 592f899d2ecc..7bf243fe7f33 100644 --- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml +++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml @@ -18,9 +18,8 @@ android:width="20dp" android:height="20dp" android:viewportWidth="960" - android:viewportHeight="960" - android:tint="?attr/colorControlNormal"> + android:viewportHeight="960"> <path - android:fillColor="?android:attr/textColorSecondary" + android:fillColor="?android:attr/textColorSecondaryInverse" android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/> </vector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values-night/styles.xml b/libs/WindowManager/Shell/res/values-night/styles.xml new file mode 100644 index 000000000000..758c99db7bb4 --- /dev/null +++ b/libs/WindowManager/Shell/res/values-night/styles.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. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="ReachabilityEduHandLayout"> + <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> + <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textAppearance"> + @*android:style/TextAppearance.DeviceDefault.Body2 + </item> + </style> + +</resources>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index ee80c4723b8d..2b3888854d5a 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -160,7 +160,7 @@ <item name="android:lineSpacingExtra">-1sp</item> <item name="android:textSize">12sp</item> <item name="android:textAlignment">center</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?android:attr/textColorPrimaryInverse</item> <item name="android:textAppearance"> @*android:style/TextAppearance.DeviceDefault.Body2 </item> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index e396ba13f154..102f2cb4b8d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -301,7 +301,8 @@ public class Bubble implements BubbleViewProvider { getIcon(), getUser().getIdentifier(), getPackageName(), - getTitle()); + getTitle(), + isImportantConversation()); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java index baa23e3040c4..21355a3efa2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java @@ -30,8 +30,6 @@ import java.util.Objects; */ public class BubbleInfo implements Parcelable { - // TODO(b/269671451): needs whether the bubble is an 'important person' or not - private String mKey; // Same key as the Notification private int mFlags; // Flags from BubbleMetadata @Nullable @@ -47,9 +45,11 @@ public class BubbleInfo implements Parcelable { private Icon mIcon; @Nullable private String mTitle; + private boolean mIsImportantConversation; public BubbleInfo(String key, int flags, @Nullable String shortcutId, @Nullable Icon icon, - int userId, String packageName, @Nullable String title) { + int userId, String packageName, @Nullable String title, + boolean isImportantConversation) { mKey = key; mFlags = flags; mShortcutId = shortcutId; @@ -57,6 +57,7 @@ public class BubbleInfo implements Parcelable { mUserId = userId; mPackageName = packageName; mTitle = title; + mIsImportantConversation = isImportantConversation; } private BubbleInfo(Parcel source) { @@ -67,6 +68,7 @@ public class BubbleInfo implements Parcelable { mUserId = source.readInt(); mPackageName = source.readString(); mTitle = source.readString(); + mIsImportantConversation = source.readBoolean(); } public String getKey() { @@ -100,6 +102,10 @@ public class BubbleInfo implements Parcelable { return mTitle; } + public boolean isImportantConversation() { + return mIsImportantConversation; + } + /** * Whether this bubble is currently being hidden from the stack. */ @@ -150,6 +156,7 @@ public class BubbleInfo implements Parcelable { parcel.writeInt(mUserId); parcel.writeString(mPackageName); parcel.writeString(mTitle); + parcel.writeBoolean(mIsImportantConversation); } @NonNull diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java index f65c26ada04d..d44b4d8f63b6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java @@ -21,7 +21,6 @@ import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import android.widget.ImageButton; import android.widget.LinearLayout; @@ -113,14 +112,6 @@ class CompatUILayout extends LinearLayout { } @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mWindowManager.relayout(); - } - return super.onInterceptTouchEvent(ev); - } - - @Override protected void onFinishInflate() { super.onFinishInflate(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index d4778fa7a58a..065806df3dc8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -204,14 +204,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); final int positionY = taskStableBounds.bottom - taskBounds.top - mLayout.getMeasuredHeight(); - // To secure a proper visualisation, we hide the layout while updating the position of - // the {@link SurfaceControl} it belongs. - final int oldVisibility = mLayout.getVisibility(); - if (oldVisibility == View.VISIBLE) { - mLayout.setVisibility(View.GONE); - } updateSurfacePosition(positionX, positionY); - mLayout.setVisibility(oldVisibility); } private void updateVisibilityOfViews() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 1d7e64988359..bbfeb90704db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -52,7 +52,7 @@ import java.util.Objects; */ public class PipAnimationController { static final float FRACTION_START = 0f; - private static final float FRACTION_END = 1f; + static final float FRACTION_END = 1f; public static final int ANIM_TYPE_BOUNDS = 0; public static final int ANIM_TYPE_ALPHA = 1; @@ -718,7 +718,9 @@ public class PipAnimationController { .round(tx, leash, sourceBounds, bounds) .shadow(tx, leash, shouldApplyShadowRadius()); } - tx.apply(); + if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) { + tx.apply(); + } } private Rect computeInsets(float fraction) { 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 363d6759f8d0..8709eaba0272 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 @@ -828,6 +828,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void onEndOfSwipePipToHomeTransition() { if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.setEnterAnimationType(ANIM_TYPE_BOUNDS); return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 046d6fce443b..db516c0e74f9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -45,7 +45,6 @@ import android.animation.Animator; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; -import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; @@ -109,6 +108,17 @@ public class PipTransition extends PipTransitionController { /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; + /** Used for setting transform to a transaction from animator. */ + private final PipAnimationController.PipTransactionHandler mTransactionConsumer = + new PipAnimationController.PipTransactionHandler() { + @Override + public boolean handlePipTransaction(SurfaceControl leash, + SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { + // Only set the operation to transaction but do not apply. + return true; + } + }; + public PipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @@ -338,7 +348,7 @@ public class PipTransition extends PipTransitionController { @Override public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, - @Nullable SurfaceControl.Transaction tx) { + @NonNull SurfaceControl.Transaction tx) { final boolean enteringPip = isInPipDirection(direction); if (enteringPip) { mPipTransitionState.setTransitionState(ENTERED_PIP); @@ -348,13 +358,15 @@ public class PipTransition extends PipTransitionController { // (likely a remote like launcher), so don't fire the finish-callback here -- wait until // the exit transition is merged. if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) { + final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); + final boolean hasValidLeash = leash != null && leash.isValid(); WindowContainerTransaction wct = null; if (isOutPipDirection(direction)) { // Only need to reset surface properties. The server-side operations were already // done at the start. But if it is running fixed rotation, there will be a seamless // display transition later. So the last rotation transform needs to be kept to // avoid flickering, and then the display transition will reset the transform. - if (tx != null && !mInFixedRotation) { + if (!mInFixedRotation && mFinishTransaction != null) { mFinishTransaction.merge(tx); } } else { @@ -363,27 +375,36 @@ public class PipTransition extends PipTransitionController { // If we are animating from fullscreen using a bounds animation, then reset the // activity windowing mode, and set the task bounds to the final bounds wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); - wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds); wct.setBounds(taskInfo.token, destinationBounds); } else { wct.setBounds(taskInfo.token, null /* bounds */); } - if (tx != null) { - wct.setBoundsChangeTransaction(taskInfo.token, tx); + // Reset the scale with bounds change synchronously. + if (hasValidLeash) { + mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) + .resetScale(tx, leash, destinationBounds) + .round(tx, leash, true /* applyCornerRadius */); } + wct.setBoundsChangeTransaction(taskInfo.token, tx); } - final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final int displayRotation = taskInfo.getConfiguration().windowConfiguration .getDisplayRotation(); if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation - && leash != null && leash.isValid()) { + && hasValidLeash) { // Launcher may update the Shelf height during the animation, which will update the // destination bounds. Because this is in fixed rotation, We need to make sure the // finishTransaction is using the updated bounds in the display rotation. + final PipAnimationController.PipTransitionAnimator<?> animator = + mPipAnimationController.getCurrentAnimator(); final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); final Rect finishBounds = new Rect(destinationBounds); rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); - mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds); + if (!finishBounds.equals(animator.getEndValue())) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Destination bounds were changed during animation", TAG); + rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); + mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds); + } } mFinishTransaction = null; callFinishCallback(wct); @@ -665,9 +686,11 @@ public class PipTransition extends PipTransitionController { private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect baseBounds, final Rect startBounds, final Rect endBounds, final int rotationDelta) { + final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( + taskInfo.pictureInPictureParams, endBounds); final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, - endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP, + endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setPipAnimationCallback(mPipAnimationCallback) @@ -800,10 +823,6 @@ public class PipTransition extends PipTransitionController { computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); } - // Set corner radius for entering pip. - mSurfaceTransactionHelper - .crop(finishTransaction, leash, destinationBounds) - .round(finishTransaction, leash, true /* applyCornerRadius */); if (!mPipOrganizer.shouldAttachMenuEarly()) { mTransitions.getMainExecutor().executeDelayed( () -> mPipMenuController.attach(leash), 0); @@ -812,41 +831,11 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; - startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) - .setPosition(leash, destinationBounds.left, destinationBounds.top) - .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height()); - if (swipePipToHomeOverlay != null) { - // Launcher fade in the overlay on top of the fullscreen Task. It is possible we - // reparent the PIP activity to a new PIP task (in case there are other activities - // in the original Task), so we should also reparent the overlay to the PIP task. - startTransaction.reparent(swipePipToHomeOverlay, leash) - .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); - mPipOrganizer.mSwipePipToHomeOverlay = null; - } - startTransaction.apply(); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { - // For fixed rotation, set the destination bounds to the new rotation coordinates - // at the end. - destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); - } - mPipBoundsState.setBounds(destinationBounds); - onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */); - sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); - if (swipePipToHomeOverlay != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, - null /* callback */, false /* withStartDelay */); - } - mPipTransitionState.setInSwipePipToHomeTransition(false); + handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash, + sourceHintRect, destinationBounds, rotationDelta, taskInfo); return; } - if (rotationDelta != Surface.ROTATION_0) { - Matrix tmpTransform = new Matrix(); - tmpTransform.postRotate(rotationDelta); - startTransaction.setMatrix(leash, tmpTransform, new float[9]); - } - final int enterAnimationType = mEnterAnimationType; if (enterAnimationType == ANIM_TYPE_ALPHA) { startTransaction.setAlpha(leash, 0f); @@ -880,11 +869,13 @@ public class PipTransition extends PipTransitionController { } else if (enterAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); + mSurfaceTransactionHelper + .crop(finishTransaction, leash, destinationBounds) + .round(finishTransaction, leash, true /* applyCornerRadius */); } else { throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) - .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { @@ -893,7 +884,15 @@ public class PipTransition extends PipTransitionController { // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); } - animator.start(); + // Keep the last appearance when finishing the transition. The transform will be reset when + // setting bounds. + animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction( + leash, finishTransaction, PipAnimationController.FRACTION_END); + // Remove the workaround after fixing ClosePipBySwipingDownTest that detects the shadow + // as unexpected visible. + finishTransaction.setShadowRadius(leash, 0); + // Start to animate enter PiP. + animator.setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()).start(); } /** Computes destination bounds in old rotation and updates source hint rect if available. */ @@ -915,6 +914,51 @@ public class PipTransition extends PipTransitionController { } } + private void handleSwipePipToHomeTransition( + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, + @NonNull Rect destinationBounds, int rotationDelta, + @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { + final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; + if (swipePipToHomeOverlay != null) { + // Launcher fade in the overlay on top of the fullscreen Task. It is possible we + // reparent the PIP activity to a new PIP task (in case there are other activities + // in the original Task), so we should also reparent the overlay to the PIP task. + startTransaction.reparent(swipePipToHomeOverlay, leash) + .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); + mPipOrganizer.mSwipePipToHomeOverlay = null; + } + + Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); + if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) { + // PipController#startSwipePipToHome has updated the display layout to new rotation, + // so flip the source bounds to match the same orientation. + sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width()); + } + final PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, + destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, + 0 /* startingAngle */, 0 /* rotationDelta */) + .setPipTransactionHandler(mTransactionConsumer) + .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP); + // The start state is the end state for swipe-auto-pip. + startTransaction.merge(finishTransaction); + animator.applySurfaceControlTransaction(leash, startTransaction, + PipAnimationController.FRACTION_END); + startTransaction.apply(); + + mPipBoundsState.setBounds(destinationBounds); + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx); + sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); + if (swipePipToHomeOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, + null /* callback */, false /* withStartDelay */); + } + mPipTransitionState.setInSwipePipToHomeTransition(false); + } + private void startExitToSplitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, 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 af52350f5b48..c654c1b0d431 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 @@ -337,6 +337,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); } + /** Check whether the task is the single-top root or the root of one of the stages. */ + public boolean isTaskRootOrStageRoot(int taskId) { + return mStageCoordinator.isRootOrStageRoot(taskId); + } + public @SplitPosition int getSplitPosition(int taskId) { return mStageCoordinator.getSplitPosition(taskId); } 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 b8373f3548ca..749549d1ca55 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 @@ -377,6 +377,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } + boolean isRootOrStageRoot(int taskId) { + if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) { + return true; + } + return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId); + } + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, WindowContainerTransaction wct) { prepareEnterSplitScreen(wct, task, stagePosition); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index 18b09b090794..e2e9270cd6cc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -286,6 +286,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + boolean isRootTaskId(int taskId) { + return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; + } + void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately) { if (mSplitDecorManager != null && mRootTaskInfo != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java index 96657af22e37..9d4e6b475c53 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java @@ -20,6 +20,7 @@ import static android.graphics.Color.blue; import static android.graphics.Color.green; import static android.graphics.Color.red; +import android.annotation.ColorRes; import android.annotation.NonNull; import android.content.Context; import android.view.SurfaceControl; @@ -33,10 +34,14 @@ public class UnfoldBackgroundController { private static final int BACKGROUND_LAYER_Z_INDEX = -1; private final float[] mBackgroundColor; + private final float[] mSplitScreenBackgroundColor; + private float[] mBackgroundColorSet; private SurfaceControl mBackgroundLayer; + private boolean mSplitScreenVisible = false; public UnfoldBackgroundController(@NonNull Context context) { - mBackgroundColor = getBackgroundColor(context); + mBackgroundColor = getRGBColorFromId(context, R.color.unfold_background); + mSplitScreenBackgroundColor = getRGBColorFromId(context, R.color.split_divider_background); } /** @@ -44,7 +49,14 @@ public class UnfoldBackgroundController { * @param transaction where we should add the background if it is not added */ public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) { - if (mBackgroundLayer != null) return; + float[] expectedColor = getCurrentBackgroundColor(); + if (mBackgroundLayer != null) { + if (mBackgroundColorSet != expectedColor) { + transaction.setColor(mBackgroundLayer, expectedColor); + mBackgroundColorSet = expectedColor; + } + return; + } SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() .setName("app-unfold-background") @@ -53,9 +65,10 @@ public class UnfoldBackgroundController { mBackgroundLayer = colorLayerBuilder.build(); transaction - .setColor(mBackgroundLayer, mBackgroundColor) + .setColor(mBackgroundLayer, expectedColor) .show(mBackgroundLayer) .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX); + mBackgroundColorSet = expectedColor; } /** @@ -70,8 +83,25 @@ public class UnfoldBackgroundController { mBackgroundLayer = null; } - private float[] getBackgroundColor(Context context) { - int colorInt = context.getResources().getColor(R.color.unfold_background); + /** + * Expected to be called whenever split screen visibility changes. + * + * @param visible True when split screen is visible + */ + public void onSplitVisibilityChanged(boolean visible) { + mSplitScreenVisible = visible; + } + + private float[] getCurrentBackgroundColor() { + if (mSplitScreenVisible) { + return mSplitScreenBackgroundColor; + } else { + return mBackgroundColor; + } + } + + private float[] getRGBColorFromId(Context context, @ColorRes int id) { + int colorInt = context.getResources().getColor(id); return new float[]{ (float) red(colorInt) / 255.0F, (float) green(colorInt) / 255.0F, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 2f0c96487d68..123bf3bfca2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -292,6 +292,11 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, .setCornerRadius(context.mLeash, 0.0F); } + @Override + public void onSplitVisibilityChanged(boolean visible) { + mUnfoldBackgroundController.onSplitVisibilityChanged(visible); + } + private class AnimationContext { final SurfaceControl mLeash; 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 0b821844e46c..54babce13205 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 @@ -760,6 +760,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; + if (mSplitScreenController.isPresent() + && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) { + return false; + } return DesktopModeStatus.isProto2Enabled() && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && mDisplayController.getDisplayContext(taskInfo.displayId) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt index 8fbac1db8653..432909f18813 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/bubbles/BubbleInfoTest.kt @@ -31,7 +31,8 @@ class BubbleInfoTest : ShellTestCase() { @Test fun bubbleInfo() { - val bubbleInfo = BubbleInfo("key", 0, "shortcut id", null, 6, "com.some.package", "title") + val bubbleInfo = + BubbleInfo("key", 0, "shortcut id", null, 6, "com.some.package", "title", true) val parcel = Parcel.obtain() bubbleInfo.writeToParcel(parcel, PARCELABLE_WRITE_RETURN_VALUE) parcel.setDataPosition(0) @@ -45,5 +46,7 @@ class BubbleInfoTest : ShellTestCase() { assertThat(bubbleInfo.userId).isEqualTo(bubbleInfoFromParcel.userId) assertThat(bubbleInfo.packageName).isEqualTo(bubbleInfoFromParcel.packageName) assertThat(bubbleInfo.title).isEqualTo(bubbleInfoFromParcel.title) + assertThat(bubbleInfo.isImportantConversation) + .isEqualTo(bubbleInfoFromParcel.isImportantConversation) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 55781f1b4d6f..78c3cbdaace6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -28,7 +28,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -363,14 +362,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { mWindowManager.updateVisibility(/* canShow= */ false); verify(mWindowManager, never()).createLayout(anyBoolean()); - verify(mLayout, atLeastOnce()).setVisibility(View.GONE); + verify(mLayout).setVisibility(View.GONE); // Show button. doReturn(View.GONE).when(mLayout).getVisibility(); mWindowManager.updateVisibility(/* canShow= */ true); verify(mWindowManager, never()).createLayout(anyBoolean()); - verify(mLayout, atLeastOnce()).setVisibility(View.VISIBLE); + verify(mLayout).setVisibility(View.VISIBLE); } @Test diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 34af1f9ea039..db581471e2ca 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -129,6 +129,7 @@ cc_defaults { "libandroidfw", "libcrypto", "libsync", + "libui", ], static_libs: [ "libEGL_blobCache", diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index b3eaa0ce5979..92d875bf7f1e 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -18,6 +18,10 @@ #include "HardwareBitmapUploader.h" #include "Properties.h" #ifdef __ANDROID__ // Layoutlib does not support render thread +#include <private/android/AHardwareBufferHelpers.h> +#include <ui/GraphicBuffer.h> +#include <ui/GraphicBufferMapper.h> + #include "renderthread/RenderProxy.h" #endif #include "utils/Color.h" @@ -51,6 +55,34 @@ namespace android { +#ifdef __ANDROID__ +static uint64_t AHardwareBuffer_getAllocationSize(AHardwareBuffer* aHardwareBuffer) { + GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(aHardwareBuffer); + auto& mapper = GraphicBufferMapper::get(); + uint64_t size = 0; + auto err = mapper.getAllocationSize(buffer->handle, &size); + if (err == OK) { + if (size > 0) { + return size; + } else { + ALOGW("Mapper returned size = 0 for buffer format: 0x%x size: %d x %d", buffer->format, + buffer->width, buffer->height); + // Fall-through to estimate + } + } + + // Estimation time! + // Stride could be = 0 if it's ill-defined (eg, compressed buffer), in which case we use the + // width of the buffer instead + size = std::max(buffer->width, buffer->stride) * buffer->height; + // Require bpp to be at least 1. This is too low for many formats, but it's better than 0 + // Also while we could make increasingly better estimates, the reality is that mapper@4 + // should be common enough at this point that we won't ever hit this anyway + size *= std::max(1u, bytesPerPixel(buffer->format)); + return size; +} +#endif + bool Bitmap::computeAllocationSize(size_t rowBytes, int height, size_t* size) { return 0 <= height && height <= std::numeric_limits<size_t>::max() && !__builtin_mul_overflow(rowBytes, (size_t)height, size) && @@ -261,6 +293,7 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes , mPalette(palette) , mPaletteGenerationId(getGenerationID()) { mPixelStorage.hardware.buffer = buffer; + mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer); AHardwareBuffer_acquire(buffer); setImmutable(); // HW bitmaps are always immutable mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace()); @@ -317,6 +350,10 @@ size_t Bitmap::getAllocationByteCount() const { return mPixelStorage.heap.size; case PixelStorageType::Ashmem: return mPixelStorage.ashmem.size; +#ifdef __ANDROID__ + case PixelStorageType::Hardware: + return mPixelStorage.hardware.size; +#endif default: return rowBytes() * height(); } diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 912d311c3678..dd344e2f5517 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -221,6 +221,7 @@ private: #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration struct { AHardwareBuffer* buffer; + uint64_t size; } hardware; #endif } mPixelStorage; diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl index d9d16ec30bff..58aed4aea76c 100644 --- a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl @@ -21,6 +21,7 @@ import android.media.soundtrigger.PhraseRecognitionEvent; * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the * framework. */ +@JavaDerive(equals = true, toString = true) parcelable PhraseRecognitionEventSys { PhraseRecognitionEvent phraseRecognitionEvent; diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl index 20ec8c236dcc..dc22f68a389e 100644 --- a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl +++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl @@ -21,6 +21,7 @@ import android.media.soundtrigger.RecognitionEvent; * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the * framework. */ +@JavaDerive(equals = true, toString = true) parcelable RecognitionEventSys { RecognitionEvent recognitionEvent; diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 78ee81921f25..c85ffd459bd8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -38,6 +38,7 @@ import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.findAutoSelectEntry import com.android.credentialmanager.common.ProviderActivityState +import com.android.credentialmanager.createflow.isFlowAutoSelectable /** * Client for interacting with Credential Manager. Also holds data inputs from it. @@ -113,21 +114,30 @@ class CredentialManagerRepo( val providerDisableListUiState = getCreateProviderDisableListInitialUiState() val requestDisplayInfoUiState = getCreateRequestDisplayInfoInitialUiState(originName)!! + val createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( + enabledProviders = providerEnableListUiState, + disabledProviders = providerDisableListUiState, + defaultProviderIdPreferredByApp = + requestDisplayInfoUiState.appPreferredDefaultProviderId, + defaultProviderIdsSetByUser = + requestDisplayInfoUiState.userSetDefaultProviderIds, + requestDisplayInfo = requestDisplayInfoUiState, + isOnPasskeyIntroStateAlready = false, + isPasskeyFirstUse = isPasskeyFirstUse, + )!! + val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState) UiState( - createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( - enabledProviders = providerEnableListUiState, - disabledProviders = providerDisableListUiState, - defaultProviderIdPreferredByApp = - requestDisplayInfoUiState.appPreferredDefaultProviderId, - defaultProviderIdsSetByUser = - requestDisplayInfoUiState.userSetDefaultProviderIds, - requestDisplayInfo = requestDisplayInfoUiState, - isOnPasskeyIntroStateAlready = false, - isPasskeyFirstUse = isPasskeyFirstUse, - )!!, + createCredentialUiState = createCredentialUiState, getCredentialUiState = null, cancelRequestState = cancelUiRequestState, isInitialRender = isNewActivity, + isAutoSelectFlow = isFlowAutoSelectable, + providerActivityState = + if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH + else ProviderActivityState.NOT_APPLICABLE, + selectedEntry = + if (isFlowAutoSelectable) createCredentialUiState.activeEntry?.activeEntryInfo + else null, ) } RequestInfo.TYPE_GET -> { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index e96e536c00d5..cf962d1d94aa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -34,6 +34,7 @@ import com.android.credentialmanager.common.DialogState import com.android.credentialmanager.common.ProviderActivityResult import com.android.credentialmanager.common.ProviderActivityState import com.android.credentialmanager.createflow.ActiveEntry +import com.android.credentialmanager.createflow.isFlowAutoSelectable import com.android.credentialmanager.createflow.CreateCredentialUiState import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.getflow.GetCredentialUiState @@ -238,7 +239,8 @@ class CredentialSelectorViewModel( getCredentialUiState = uiState.getCredentialUiState?.copy( currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS, isNoAccount = isNoAccount, - ) + ), + isInitialRender = true, ) } @@ -262,30 +264,39 @@ class CredentialSelectorViewModel( /***** Create Flow Callbacks *****/ /**************************************************************************/ fun createFlowOnConfirmIntro() { + userConfigRepo.setIsPasskeyFirstUse(false) val prevUiState = uiState.createCredentialUiState if (prevUiState == null) { Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state") onInternalError() return } - val newUiState = CreateFlowUtils.toCreateCredentialUiState( - enabledProviders = prevUiState.enabledProviders, - disabledProviders = prevUiState.disabledProviders, - defaultProviderIdPreferredByApp = - prevUiState.requestDisplayInfo.appPreferredDefaultProviderId, - defaultProviderIdsSetByUser = - prevUiState.requestDisplayInfo.userSetDefaultProviderIds, - requestDisplayInfo = prevUiState.requestDisplayInfo, + val newScreenState = CreateFlowUtils.toCreateScreenState( + createOptionSize = prevUiState.sortedCreateOptionsPairs.size, isOnPasskeyIntroStateAlready = true, - isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() + requestDisplayInfo = prevUiState.requestDisplayInfo, + remoteEntry = prevUiState.remoteEntry, + isPasskeyFirstUse = true, ) - if (newUiState == null) { - Log.d(Constants.LOG_TAG, "Unable to update create ui state") + if (newScreenState == null) { + Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state") onInternalError() return } - uiState = uiState.copy(createCredentialUiState = newUiState) - userConfigRepo.setIsPasskeyFirstUse(false) + val newCreateCredentialUiState = prevUiState.copy( + currentScreenState = newScreenState, + ) + val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState) + uiState = uiState.copy( + createCredentialUiState = newCreateCredentialUiState, + isAutoSelectFlow = isFlowAutoSelectable, + providerActivityState = + if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH + else ProviderActivityState.NOT_APPLICABLE, + selectedEntry = + if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo + else null, + ) } fun createFlowOnMoreOptionsSelectedOnCreationSelection() { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index ba20a5e779fd..70634f435ef1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -17,7 +17,6 @@ package com.android.credentialmanager import android.app.slice.Slice -import android.app.slice.SliceItem import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager @@ -201,7 +200,8 @@ class GetFlowUtils { ComponentName::class.java ) val preferTopBrandingContent: TopBrandingContent? = - if (preferUiBrandingComponentName == null) null + if (!requestInfo.hasPermissionToOverrideDefault() || + preferUiBrandingComponentName == null) null else { val (displayName, icon) = getServiceLabelAndIcon( context.packageManager, preferUiBrandingComponentName.flattenToString()) @@ -335,12 +335,8 @@ class GetFlowUtils { val structuredAuthEntry = AuthenticationAction.fromSlice(entry.slice) ?: return@forEach - // TODO: replace with official jetpack code. - val titleItem: SliceItem? = entry.slice.items.firstOrNull { - it.hasHint( - "androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE") - } - val title: String = titleItem?.text?.toString() ?: providerDisplayName + val title: String = + structuredAuthEntry.title.toString().ifEmpty { providerDisplayName } result.add(AuthenticationEntryInfo( providerId = providerId, @@ -487,6 +483,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) is CreatePublicKeyCredentialRequest -> { newRequestDisplayInfoFromPasskeyJson( @@ -497,6 +494,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } is CreateCustomCredentialRequest -> { @@ -515,6 +513,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } else -> null @@ -602,7 +601,7 @@ class CreateFlowUtils { ) } - private fun toCreateScreenState( + fun toCreateScreenState( createOptionSize: Int, isOnPasskeyIntroStateAlready: Boolean, requestDisplayInfo: RequestDisplayInfo, @@ -662,7 +661,13 @@ class CreateFlowUtils { passkeyCount = createEntry.getPublicKeyCredentialCount(), totalCredentialCount = createEntry.getTotalCredentialCount(), lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN, - footerDescription = createEntry.description?.toString() + footerDescription = createEntry.description?.toString(), + // TODO(b/281065680): replace with official library constant once available + allowAutoSelect = + it.slice.items.firstOrNull { + it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" + + "SELECT_ALLOWED") + }?.text == "true", )) } return result.sortedWith( @@ -694,6 +699,7 @@ class CreateFlowUtils { preferImmediatelyAvailableCredentials: Boolean, appPreferredDefaultProviderId: String?, userSetDefaultProviderIds: Set<String>, + isAutoSelectRequest: Boolean ): RequestDisplayInfo? { val json = JSONObject(requestJson) var passkeyUsername = "" @@ -716,6 +722,7 @@ class CreateFlowUtils { preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId, userSetDefaultProviderIds, + isAutoSelectRequest, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt index bd4375f4513e..a5998faa68ad 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt @@ -39,14 +39,16 @@ fun ModalBottomSheet( onDismiss: () -> Unit, isInitialRender: Boolean, onInitialRenderComplete: () -> Unit, + isAutoSelectFlow: Boolean, ) { val scope = rememberCoroutineScope() val state = rememberModalBottomSheetState( - initialValue = ModalBottomSheetValue.Hidden, + initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded + else ModalBottomSheetValue.Hidden, skipHalfExpanded = true ) val sysUiController = rememberSystemUiController() - if (state.targetValue == ModalBottomSheetValue.Hidden) { + if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) { setTransparentSystemBarsColor(sysUiController) } else { setBottomSheetSystemBarsColor(sysUiController) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 2dba2ab6777c..1c394ec4ba55 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -255,7 +255,7 @@ fun ActionEntry( Column(modifier = Modifier.wrapContentSize() .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) { SmallTitleText(entryHeadlineText) - if (entrySecondLineText != null) { + if (entrySecondLineText != null && entrySecondLineText.isNotEmpty()) { BodySmallText(entrySecondLineText) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index a378f1c16753..a3087cf8004c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -166,6 +166,7 @@ fun CreateCredentialScreen( }, onDismiss = viewModel::onUserCancel, isInitialRender = viewModel.uiState.isInitialRender, + isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow, onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 8f3f3c87d93c..cf7a943f2c66 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -34,6 +34,21 @@ data class CreateCredentialUiState( val foundCandidateFromUserDefaultProvider: Boolean, ) +internal fun isFlowAutoSelectable( + uiState: CreateCredentialUiState +): Boolean { + return uiState.requestDisplayInfo.isAutoSelectRequest && + // Even if the flow is auto selectable, still allow passkey intro screen to show once if + // applicable. + uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO && + uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO && + uiState.remoteEntry == null && + uiState.sortedCreateOptionsPairs.size == 1 && + uiState.activeEntry?.activeEntryInfo?.let { + it is CreateOptionInfo && it.allowAutoSelect + } ?: false +} + internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean { return state.sortedCreateOptionsPairs.isNotEmpty() || (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials && @@ -74,6 +89,7 @@ class CreateOptionInfo( val totalCredentialCount: Int?, val lastUsedTime: Instant, val footerDescription: String?, + val allowAutoSelect: Boolean, ) : BaseEntry( providerId, entryKey, @@ -107,6 +123,8 @@ data class RequestDisplayInfo( val preferImmediatelyAvailableCredentials: Boolean, val appPreferredDefaultProviderId: String?, val userSetDefaultProviderIds: Set<String>, + // Whether the given CreateCredentialRequest allows auto select. + val isAutoSelectRequest: Boolean, ) /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 40c99ee07d69..934b0aebf417 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -149,6 +149,7 @@ fun GetCredentialScreen( }, onDismiss = viewModel::onUserCancel, isInitialRender = viewModel.uiState.isInitialRender, + isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow, onInitialRenderComplete = viewModel::onInitialRenderComplete, ) } @@ -560,7 +561,12 @@ fun AuthenticationEntryRow( enforceOneLine: Boolean = false, ) { Entry( - onClick = { onEntrySelected(authenticationEntryInfo) }, + onClick = if (authenticationEntryInfo.isUnlockedAndEmpty) { + {} + } // No-op + else { + { onEntrySelected(authenticationEntryInfo) } + }, iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(), entryHeadlineText = authenticationEntryInfo.title, entrySecondLineText = stringResource( diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index af3e2102e430..d778febe9faf 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -43,6 +43,9 @@ android_app { compile_multilib: "both", jni_libs: ["libshim_jni"], + // Explicitly uncompress native libs rather than letting the build system doing it and destroy the + // v2/v3 signature. + use_embedded_native_libs: true, uses_libs: ["android.test.runner"], @@ -124,6 +127,9 @@ android_app { compile_multilib: "both", jni_libs: ["libshim_jni"], + // Explicitly uncompress native libs rather than letting the build system doing it and destroy the + // v2/v3 signature. + use_embedded_native_libs: true, uses_libs: ["android.test.runner"], } diff --git a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm index 1346bbb489c1..90041da529c0 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_ukrainian.kcm @@ -36,8 +36,7 @@ key GRAVE { key 1 { label: '1' base: '1' - shift: '!' - ralt: '!' + shift, ralt: '!' } key 2 { @@ -64,8 +63,7 @@ key 4 { key 5 { label: '5' base: '5' - shift: '%' - ralt: '%' + shift, ralt: '%' } key 6 { @@ -85,22 +83,19 @@ key 7 { key 8 { label: '8' base: '8' - shift: '*' - ralt: '*' + shift, ralt: '*' } key 9 { label: '9' base: '9' - shift: '(' - ralt: '(' + shift, ralt: '(' } key 0 { label: '0' base: '0' - shift: ')' - ralt: ')' + shift, ralt: ')' } key MINUS { @@ -108,7 +103,7 @@ key MINUS { base: '-' shift: '_' ralt: '-' - ralt+shift: '_' + shift+ralt: '_' } key EQUALS { @@ -116,7 +111,7 @@ key EQUALS { base: '=' shift: '+' ralt: '=' - ralt+shift: '+' + shift+ralt: '+' } ### ROW 2 @@ -126,9 +121,6 @@ key Q { base: '\u0439' shift, capslock: '\u0419' shift+capslock: '\u0439' - ralt: 'q' - shift+ralt, capslock+ralt: 'Q' - shift+capslock+ralt: 'q' } key W { @@ -136,9 +128,6 @@ key W { base: '\u0446' shift, capslock: '\u0426' shift+capslock: '\u0446' - ralt: 'w' - shift+ralt, capslock+ralt: 'W' - shift+capslock+ralt: 'w' } key E { @@ -146,9 +135,6 @@ key E { base: '\u0443' shift, capslock: '\u0423' shift+capslock: '\u0443' - ralt: 'e' - shift+ralt, capslock+ralt: 'E' - shift+capslock+ralt: 'e' } key R { @@ -156,9 +142,6 @@ key R { base: '\u043a' shift, capslock: '\u041a' shift+capslock: '\u043a' - ralt: 'r' - shift+ralt, capslock+ralt: 'R' - shift+capslock+ralt: 'r' } key T { @@ -166,9 +149,6 @@ key T { base: '\u0435' shift, capslock: '\u0415' shift+capslock: '\u0435' - ralt: 't' - shift+ralt, capslock+ralt: 'T' - shift+capslock+ralt: 't' } key Y { @@ -176,9 +156,6 @@ key Y { base: '\u043d' shift, capslock: '\u041d' shift+capslock: '\u043d' - ralt: 'y' - shift+ralt, capslock+ralt: 'Y' - shift+capslock+ralt: 'y' } key U { @@ -186,9 +163,9 @@ key U { base: '\u0433' shift, capslock: '\u0413' shift+capslock: '\u0433' - ralt: 'u' - shift+ralt, capslock+ralt: 'U' - shift+capslock+ralt: 'u' + ralt: '\u0491' + shift+ralt, capslock+ralt: '\u0490' + shift+capslock+ralt: '\u0491' } key I { @@ -196,9 +173,6 @@ key I { base: '\u0448' shift, capslock: '\u0428' shift+capslock: '\u0448' - ralt: 'i' - shift+ralt, capslock+ralt: 'I' - shift+capslock+ralt: 'i' } key O { @@ -206,9 +180,6 @@ key O { base: '\u0449' shift, capslock: '\u0429' shift+capslock: '\u0449' - ralt: 'o' - shift+ralt, capslock+ralt: 'O' - shift+capslock+ralt: 'o' } key P { @@ -216,9 +187,6 @@ key P { base: '\u0437' shift, capslock: '\u0417' shift+capslock: '\u0437' - ralt: 'p' - shift+ralt, capslock+ralt: 'P' - shift+capslock+ralt: 'p' } key LEFT_BRACKET { @@ -226,8 +194,6 @@ key LEFT_BRACKET { base: '\u0445' shift, capslock: '\u0425' shift+capslock: '\u0445' - ralt: '[' - ralt+shift: '{' } key RIGHT_BRACKET { @@ -235,8 +201,6 @@ key RIGHT_BRACKET { base: '\u0457' shift, capslock: '\u0407' shift+capslock: '\u0457' - ralt: ']' - ralt+shift: '}' } ### ROW 3 @@ -246,9 +210,6 @@ key A { base: '\u0444' shift, capslock: '\u0424' shift+capslock: '\u0444' - ralt: 'a' - shift+ralt, capslock+ralt: 'A' - shift+capslock+ralt: 'a' } key S { @@ -256,9 +217,6 @@ key S { base: '\u0456' shift, capslock: '\u0406' shift+capslock: '\u0456' - ralt: 's' - shift+ralt, capslock+ralt: 'S' - shift+capslock+ralt: 's' } key D { @@ -266,9 +224,6 @@ key D { base: '\u0432' shift, capslock: '\u0412' shift+capslock: '\u0432' - ralt: 'd' - shift+ralt, capslock+ralt: 'D' - shift+capslock+ralt: 'd' } key F { @@ -276,9 +231,6 @@ key F { base: '\u0430' shift, capslock: '\u0410' shift+capslock: '\u0430' - ralt: 'f' - shift+ralt, capslock+ralt: 'F' - shift+capslock+ralt: 'f' } key G { @@ -286,9 +238,6 @@ key G { base: '\u043f' shift, capslock: '\u041f' shift+capslock: '\u043f' - ralt: 'g' - shift+ralt, capslock+ralt: 'G' - shift+capslock+ralt: 'g' } key H { @@ -296,9 +245,6 @@ key H { base: '\u0440' shift, capslock: '\u0420' shift+capslock: '\u0440' - ralt: 'h' - shift+ralt, capslock+ralt: 'H' - shift+capslock+ralt: 'h' } key J { @@ -306,9 +252,6 @@ key J { base: '\u043e' shift, capslock: '\u041e' shift+capslock: '\u043e' - ralt: 'j' - shift+ralt, capslock+ralt: 'J' - shift+capslock+ralt: 'j' } key K { @@ -316,9 +259,6 @@ key K { base: '\u043b' shift, capslock: '\u041b' shift+capslock: '\u043b' - ralt: 'k' - shift+ralt, capslock+ralt: 'K' - shift+capslock+ralt: 'k' } key L { @@ -326,9 +266,6 @@ key L { base: '\u0434' shift, capslock: '\u0414' shift+capslock: '\u0434' - ralt: 'l' - shift+ralt, capslock+ralt: 'L' - shift+capslock+ralt: 'l' } key SEMICOLON { @@ -372,9 +309,6 @@ key Z { base: '\u044f' shift, capslock: '\u042f' shift+capslock: '\u044f' - ralt: 'z' - shift+ralt, capslock+ralt: 'Z' - shift+capslock+ralt: 'z' } key X { @@ -382,9 +316,6 @@ key X { base: '\u0447' shift, capslock: '\u0427' shift+capslock: '\u0447' - ralt: 'x' - shift+ralt, capslock+ralt: 'X' - shift+capslock+ralt: 'x' } key C { @@ -392,9 +323,6 @@ key C { base: '\u0441' shift, capslock: '\u0421' shift+capslock: '\u0441' - ralt: 'c' - shift+ralt, capslock+ralt: 'C' - shift+capslock+ralt: 'c' } key V { @@ -402,9 +330,6 @@ key V { base: '\u043c' shift, capslock: '\u041c' shift+capslock: '\u043c' - ralt: 'v' - shift+ralt, capslock+ralt: 'V' - shift+capslock+ralt: 'v' } key B { @@ -412,9 +337,6 @@ key B { base: '\u0438' shift, capslock: '\u0418' shift+capslock: '\u0438' - ralt: 'b' - shift+ralt, capslock+ralt: 'B' - shift+capslock+ralt: 'b' } key N { @@ -422,9 +344,6 @@ key N { base: '\u0442' shift, capslock: '\u0422' shift+capslock: '\u0442' - ralt: 'n' - shift+ralt, capslock+ralt: 'N' - shift+capslock+ralt: 'n' } key M { @@ -432,9 +351,6 @@ key M { base: '\u044c' shift, capslock: '\u042c' shift+capslock: '\u044c' - ralt: 'm' - shift+ralt, capslock+ralt: 'M' - shift+capslock+ralt: 'm' } key COMMA { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 810545c40738..f12aa26f6778 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -6,6 +6,7 @@ import android.annotation.SuppressLint; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothCsipSetCoordinator; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -174,14 +175,21 @@ public class BluetoothUtils { resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second); } + int hashCode; + if ((cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)) { + hashCode = new Integer(cachedDevice.getGroupId()).hashCode(); + } else { + hashCode = cachedDevice.getAddress().hashCode(); + } + return new Pair<>(buildBtRainbowDrawable(context, - pair.first, cachedDevice.getAddress().hashCode()), pair.second); + pair.first, hashCode), pair.second); } /** * Build Bluetooth device icon with rainbow */ - public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, + private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable, int hashCode) { final Resources resources = context.getResources(); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index bf95ab9a4c50..1aa17413d05b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1548,8 +1548,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> refresh(); } - return new Pair<>(BluetoothUtils.buildBtRainbowDrawable( - mContext, pair.first, getAddress().hashCode()), pair.second); + return BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, this); } void releaseLruCache() { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index 6cf6825e61e0..82c6f1134637 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -542,13 +542,6 @@ public class InfoMediaManager extends MediaManager { //TODO(b/148765806): use correct device type once api is ready. mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route, mPackageName, mPreferenceItemMap.get(route.getId())); - if (!TextUtils.isEmpty(mPackageName) - && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) { - mediaDevice.setState(STATE_SELECTED); - if (mCurrentConnectedDevice == null) { - mCurrentConnectedDevice = mediaDevice; - } - } break; case TYPE_BUILTIN_SPEAKER: case TYPE_USB_DEVICE: @@ -581,7 +574,13 @@ public class InfoMediaManager extends MediaManager { break; } - + if (mediaDevice != null && !TextUtils.isEmpty(mPackageName) + && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) { + mediaDevice.setState(STATE_SELECTED); + if (mCurrentConnectedDevice == null) { + mCurrentConnectedDevice = mediaDevice; + } + } if (mediaDevice != null) { mMediaDevices.add(mediaDevice); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 096932706c88..aa5f3df1b750 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -25,6 +25,8 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR; import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; +import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -1006,6 +1008,37 @@ public class InfoMediaManagerTest { } @Test + public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() { + final MediaRoute2Info route2Info = mock(MediaRoute2Info.class); + final CachedBluetoothDeviceManager cachedBluetoothDeviceManager = + mock(CachedBluetoothDeviceManager.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); + final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); + routingSessionInfos.add(sessionInfo); + + when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos); + when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID)); + when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); + when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); + when(route2Info.getId()).thenReturn(TEST_ID); + when(mLocalBluetoothManager.getCachedDeviceManager()) + .thenReturn(cachedBluetoothDeviceManager); + when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) + .thenReturn(cachedDevice); + mInfoMediaManager.mRouterManager = mRouterManager; + + mInfoMediaManager.mMediaDevices.clear(); + mInfoMediaManager.addMediaDevice(route2Info); + + MediaDevice device = mInfoMediaManager.mMediaDevices.get(0); + + assertThat(device instanceof BluetoothMediaDevice).isTrue(); + assertThat(device.getState()).isEqualTo(STATE_SELECTED); + assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device); + } + + @Test public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() { mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>()); diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 85623b26c589..753c860bd5d6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -183,6 +183,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR); VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.SHOW_KEY_PRESSES, BOOLEAN_VALIDATOR); VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR); VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(System.LOCKSCREEN_DISABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index d3a9e91c3da8..1fd84c719196 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2778,6 +2778,9 @@ class SettingsProtoDumpUtil { Settings.System.SHOW_TOUCHES, SystemSettingsProto.DevOptions.SHOW_TOUCHES); dumpSetting(s, p, + Settings.System.SHOW_KEY_PRESSES, + SystemSettingsProto.DevOptions.SHOW_KEY_PRESSES); + dumpSetting(s, p, Settings.System.POINTER_LOCATION, SystemSettingsProto.DevOptions.POINTER_LOCATION); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 2e49dd5eeba9..73123c20ded2 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -77,7 +77,8 @@ public class SettingsBackupTest { Settings.System.SCREEN_BRIGHTNESS, // removed in P Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup? - Settings.System.SHOW_TOUCHES, // bug? + Settings.System.SHOW_TOUCHES, + Settings.System.SHOW_KEY_PRESSES, Settings.System.SIP_ADDRESS_ONLY, // value, not a setting Settings.System.SIP_ALWAYS, // value, not a setting Settings.System.SYSTEM_LOCALES, // bug? diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index a27f113177c7..e2f83453d12b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -1098,5 +1098,16 @@ android:exported="true" android:permission="android.permission.CUSTOMIZE_SYSTEM_UI" /> + + <!-- TODO(b/278897602): Disable EmojiCompatInitializer until threading issues are fixed. + https://developer.android.com/reference/androidx/emoji2/text/EmojiCompatInitializer --> + <provider + android:name="androidx.startup.InitializationProvider" + android:authorities="${applicationId}.androidx-startup" + android:exported="false" + tools:node="merge"> + <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" + tools:node="remove" /> + </provider> </application> </manifest> diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index ae0a9374fd2c..19fb874ea2be 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -25,10 +25,6 @@ android:id="@+id/dream_overlay_content" android:layout_width="match_parent" android:layout_height="0dp" - android:paddingTop="@dimen/dream_overlay_container_padding_top" - android:paddingEnd="@dimen/dream_overlay_container_padding_end" - android:paddingBottom="@dimen/dream_overlay_container_padding_bottom" - android:paddingStart="@dimen/dream_overlay_container_padding_start" android:clipToPadding="false" android:clipChildren="false" app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar" diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml index 8a66f50d13b1..13425c9259de 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml @@ -34,6 +34,7 @@ android:layout_height="wrap_content"> <EditText android:id="@+id/keyboard_shortcuts_search" + android:layout_gravity="center_vertical|start" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="24dp" @@ -54,58 +55,62 @@ <ImageView android:id="@+id/keyboard_shortcuts_search_cancel" + android:layout_gravity="center_vertical|end" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="end" - android:layout_marginTop="24dp" - android:layout_marginBottom="24dp" android:layout_marginEnd="49dp" android:padding="16dp" android:contentDescription="@string/keyboard_shortcut_clear_text" android:src="@drawable/ic_shortcutlist_search_button_cancel" /> </FrameLayout> - <LinearLayout + <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> - <View - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_marginStart="37dp"/> - - <Button - android:id="@+id/shortcut_system" - android:layout_width="120dp" + android:scrollbars="none"> + <LinearLayout + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_system"/> + android:gravity="center_vertical" + android:orientation="horizontal"> + <View + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="37dp"/> - <Button - android:id="@+id/shortcut_input" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_input"/> + <Button + android:id="@+id/shortcut_system" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_system"/> - <Button - android:id="@+id/shortcut_open_apps" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_open_apps"/> + <Button + android:id="@+id/shortcut_input" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_input"/> - <Button - android:id="@+id/shortcut_specific_app" - android:layout_width="120dp" - android:layout_height="wrap_content" - android:layout_marginStart="12dp" - style="@style/ShortCutButton" - android:text="@string/keyboard_shortcut_search_category_current_app"/> - </LinearLayout> + <Button + android:id="@+id/shortcut_open_apps" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_open_apps"/> + + <Button + android:id="@+id/shortcut_specific_app" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="12dp" + style="@style/ShortCutButton" + android:text="@string/keyboard_shortcut_search_category_current_app"/> + </LinearLayout> + </HorizontalScrollView> <TextView android:id="@+id/shortcut_search_no_result" diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1252695d6bbb..26d687588263 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -892,5 +892,5 @@ <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for device entry. --> - <integer name="primary_bouncer_passive_auth_delay">250</integer> + <integer name="primary_bouncer_passive_auth_delay">500</integer> </resources> diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt index daafea8b62c7..f05152ae8418 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt @@ -17,9 +17,11 @@ package com.android.keyguard.logging import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogLevel +import com.android.systemui.log.LogLevel.DEBUG import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import javax.inject.Inject @@ -39,7 +41,7 @@ constructor( ) { logBuffer.log( TAG, - LogLevel.DEBUG, + DEBUG, { bool1 = enabled bool2 = newlyUnlocked @@ -65,7 +67,7 @@ constructor( fun trustModelEmitted(value: TrustModel) { logBuffer.log( TAG, - LogLevel.DEBUG, + DEBUG, { int1 = value.userId bool1 = value.isTrusted @@ -77,12 +79,40 @@ constructor( fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) { logBuffer.log( TAG, - LogLevel.DEBUG, + DEBUG, { bool1 = isCurrentUserTrusted }, { "isCurrentUserTrusted emitted: $bool1" } ) } + fun isCurrentUserTrustManaged(isTrustManaged: Boolean) { + logBuffer.log(TAG, DEBUG, { bool1 = isTrustManaged }, { "isTrustManaged emitted: $bool1" }) + } + + fun onTrustManagedChanged(trustManaged: Boolean, userId: Int) { + logBuffer.log( + TAG, + DEBUG, + { + bool1 = trustManaged + int1 = userId + }, + { "onTrustManagedChanged isTrustManaged: $bool1 for user: $int1" } + ) + } + + fun trustManagedModelEmitted(it: TrustManagedModel) { + logBuffer.log( + TAG, + DEBUG, + { + bool1 = it.isTrustManaged + int1 = it.userId + }, + { "trustManagedModel emitted: userId: $int1, isTrustManaged: $bool1" } + ) + } + companion object { const val TAG = "TrustRepositoryLog" } diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index aade71a83c28..be5bb07089dd 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -320,7 +320,6 @@ public class Dependency { @Inject @Main Lazy<Looper> mMainLooper; @Inject @Main Lazy<Handler> mMainHandler; @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler; - @Nullable @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail; @Inject @Main Lazy<Executor> mMainExecutor; @Inject @Background Lazy<Executor> mBackgroundExecutor; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index c83166385ac6..096d94144480 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -29,6 +29,8 @@ import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.LogContextInteractor import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl +import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor +import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.concurrency.ThreadFactory import dagger.Binds @@ -65,6 +67,11 @@ interface BiometricsModule { @SysUISingleton fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor + @Binds + @SysUISingleton + fun providesSideFpsOverlayInteractor(impl: SideFpsOverlayInteractorImpl): + SideFpsOverlayInteractor + companion object { /** Background [Executor] for HAL related operations. */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 33fb36c15c2d..c43722f2a0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -57,13 +57,8 @@ interface FingerprintPropertyRepository { /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */ val sensorType: StateFlow<FingerprintSensorType> - /** The primary sensor location relative to the default display. */ - val sensorLocation: StateFlow<SensorLocationInternal> - - // TODO(b/251476085): don't implement until we need it, but expose alternative locations as - // a map of display id -> location or similar. /** The sensor location relative to each physical display. */ - // val sensorLocations<Map<String, SensorLocationInternal>> + val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> } @SysUISingleton @@ -104,15 +99,19 @@ constructor( MutableStateFlow(FingerprintSensorType.UNKNOWN) override val sensorType = _sensorType.asStateFlow() - private val _sensorLocation: MutableStateFlow<SensorLocationInternal> = - MutableStateFlow(SensorLocationInternal.DEFAULT) - override val sensorLocation = _sensorLocation.asStateFlow() + private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> = + MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT)) + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + _sensorLocations.asStateFlow() private fun setProperties(prop: FingerprintSensorPropertiesInternal) { _sensorId.value = prop.sensorId _strength.value = sensorStrengthIntToObject(prop.sensorStrength) _sensorType.value = sensorTypeIntToObject(prop.sensorType) - _sensorLocation.value = prop.location + _sensorLocations.value = + prop.allLocations.associateBy { sensorLocationInternal -> + sensorLocationInternal.displayId + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt new file mode 100644 index 000000000000..aa85e5f3b21a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt @@ -0,0 +1,51 @@ +/* + * 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.biometrics.domain.interactor + +import android.hardware.biometrics.SensorLocationInternal +import android.util.Log +import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** Business logic for SideFps overlay offsets. */ +interface SideFpsOverlayInteractor { + + /** Get the corresponding offsets based on different displayId. */ + fun getOverlayOffsets(displayId: String): SensorLocationInternal +} + +@SysUISingleton +class SideFpsOverlayInteractorImpl +@Inject +constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) : + SideFpsOverlayInteractor { + + override fun getOverlayOffsets(displayId: String): SensorLocationInternal { + val offsets = fingerprintPropertyRepository.sensorLocations.value + return if (offsets.containsKey(displayId)) { + offsets[displayId]!! + } else { + Log.w(TAG, "No location specified for display: $displayId") + offsets[""]!! + } + } + + companion object { + private const val TAG = "SideFpsOverlayInteractorImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java index e82564dc8a89..e1dd1a69158b 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java +++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java @@ -16,9 +16,21 @@ package com.android.systemui.complication; +import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_DOWN; +import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_END; +import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_START; +import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_UP; +import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM; +import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_END; +import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_START; +import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP; import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION; import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION; -import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT; +import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_DIRECTIONAL_SPACING_DEFAULT; +import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_BOTTOM; +import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_END; +import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_START; +import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_TOP; import static com.android.systemui.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT; import android.util.Log; @@ -29,6 +41,7 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.Constraints; import com.android.systemui.R; +import com.android.systemui.complication.ComplicationLayoutParams.Direction; import com.android.systemui.complication.ComplicationLayoutParams.Position; import com.android.systemui.complication.dagger.ComplicationModule; import com.android.systemui.statusbar.CrossFadeHelper; @@ -55,6 +68,47 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll public static final String TAG = "ComplicationLayoutEng"; /** + * Container for storing and operating on a tuple of margin values. + */ + public static class Margins { + public final int start; + public final int top; + public final int end; + public final int bottom; + + /** + * Default constructor with all margins set to 0. + */ + public Margins() { + this(0, 0, 0, 0); + } + + /** + * Cosntructor to specify margin in each direction. + * @param start start margin + * @param top top margin + * @param end end margin + * @param bottom bottom margin + */ + public Margins(int start, int top, int end, int bottom) { + this.start = start; + this.top = top; + this.end = end; + this.bottom = bottom; + } + + /** + * Creates a new {@link Margins} by adding the corresponding dimensions together. + */ + public static Margins combine(Margins margins1, Margins margins2) { + return new Margins(margins1.start + margins2.start, + margins1.top + margins2.top, + margins1.end + margins2.end, + margins1.bottom + margins2.bottom); + } + } + + /** * {@link ViewEntry} is an internal container, capturing information necessary for working with * a particular {@link Complication} view. */ @@ -65,15 +119,13 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll private final Parent mParent; @Complication.Category private final int mCategory; - private final int mDefaultMargin; /** * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding * view hierarchy to be accessed without traversing the entire view tree. */ ViewEntry(View view, ComplicationLayoutParams layoutParams, - TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent, - int defaultMargin) { + TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent) { mView = view; // Views that are generated programmatically do not have a unique id assigned to them // at construction. A new id is assigned here to enable ConstraintLayout relative @@ -84,7 +136,6 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll mTouchInsetSession = touchSession; mCategory = category; mParent = parent; - mDefaultMargin = defaultMargin; touchSession.addViewToTracking(mView); } @@ -192,23 +243,8 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll break; } - if (!isRoot) { - final int margin = mLayoutParams.getMargin(mDefaultMargin); - switch(direction) { - case ComplicationLayoutParams.DIRECTION_DOWN: - params.setMargins(0, margin, 0, 0); - break; - case ComplicationLayoutParams.DIRECTION_UP: - params.setMargins(0, 0, 0, margin); - break; - case ComplicationLayoutParams.DIRECTION_END: - params.setMarginStart(margin); - break; - case ComplicationLayoutParams.DIRECTION_START: - params.setMarginEnd(margin); - break; - } - } + final Margins margins = mParent.getMargins(this, isRoot); + params.setMarginsRelative(margins.start, margins.top, margins.end, margins.bottom); }); if (mLayoutParams.constraintSpecified()) { @@ -275,7 +311,6 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll private final ComplicationLayoutParams mLayoutParams; private final int mCategory; private Parent mParent; - private int mDefaultMargin; Builder(View view, TouchInsetManager.TouchInsetSession touchSession, ComplicationLayoutParams lp, @Complication.Category int category) { @@ -311,20 +346,10 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll } /** - * Sets the margin that will be applied in the direction the complication is laid out - * towards. - */ - Builder setDefaultMargin(int margin) { - mDefaultMargin = margin; - return this; - } - - /** * Builds and returns the resulting {@link ViewEntry}. */ ViewEntry build() { - return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent, - mDefaultMargin); + return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent); } } @@ -336,6 +361,11 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll * Indicates the {@link ViewEntry} requests removal. */ void removeEntry(ViewEntry entry); + + /** + * Returns the margins to be applied to the entry + */ + Margins getMargins(ViewEntry entry, boolean isRoot); } } @@ -347,6 +377,15 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll private static class PositionGroup implements DirectionGroup.Parent { private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>(); + private final HashMap<Integer, Margins> mDirectionalMargins; + + private final int mDefaultDirectionalSpacing; + + PositionGroup(int defaultDirectionalSpacing, HashMap<Integer, Margins> directionalMargins) { + mDefaultDirectionalSpacing = defaultDirectionalSpacing; + mDirectionalMargins = directionalMargins; + } + /** * Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to * this group. It is assumed that the caller has correctly identified this @@ -370,6 +409,26 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll updateViews(); } + @Override + public int getDefaultDirectionalSpacing() { + return mDefaultDirectionalSpacing; + } + + @Override + public Margins getMargins(ViewEntry entry, boolean isRoot) { + if (isRoot) { + Margins cumulativeMargins = new Margins(); + + for (Margins margins : mDirectionalMargins.values()) { + cumulativeMargins = Margins.combine(margins, cumulativeMargins); + } + + return cumulativeMargins; + } + + return mDirectionalMargins.get(entry.getLayoutParams().getDirection()); + } + private void updateViews() { ViewEntry head = null; @@ -417,14 +476,22 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll * {@link DirectionGroup}. */ void onEntriesChanged(); + + /** + * Returns the default spacing between elements. + */ + int getDefaultDirectionalSpacing(); + + /** + * Returns the margins for the view entry. + */ + Margins getMargins(ViewEntry entry, boolean isRoot); } private final ArrayList<ViewEntry> mViews = new ArrayList<>(); private final Parent mParent; /** - * Creates a new {@link DirectionGroup} with the specified parent. Note that the - * {@link DirectionGroup} does not store its own direction. It is the responsibility of the - * {@link DirectionGroup.Parent} to maintain this association. + * Creates a new {@link DirectionGroup} with the specified parent. */ DirectionGroup(Parent parent) { mParent = parent; @@ -463,6 +530,33 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll mParent.onEntriesChanged(); } + @Override + public Margins getMargins(ViewEntry entry, boolean isRoot) { + int directionalSpacing = entry.getLayoutParams().getDirectionalSpacing( + mParent.getDefaultDirectionalSpacing()); + + Margins margins = new Margins(); + + if (!isRoot) { + switch (entry.getLayoutParams().getDirection()) { + case ComplicationLayoutParams.DIRECTION_START: + margins = new Margins(0, 0, directionalSpacing, 0); + break; + case ComplicationLayoutParams.DIRECTION_UP: + margins = new Margins(0, 0, 0, directionalSpacing); + break; + case ComplicationLayoutParams.DIRECTION_END: + margins = new Margins(directionalSpacing, 0, 0, 0); + break; + case ComplicationLayoutParams.DIRECTION_DOWN: + margins = new Margins(0, directionalSpacing, 0, 0); + break; + } + } + + return Margins.combine(mParent.getMargins(entry, isRoot), margins); + } + /** * Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with * the specified head. Note that the head might not be in this group and instead part of a @@ -484,25 +578,70 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll } private final ConstraintLayout mLayout; - private final int mDefaultMargin; + private final int mDefaultDirectionalSpacing; private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>(); private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>(); private final TouchInsetManager.TouchInsetSession mSession; private final int mFadeInDuration; private final int mFadeOutDuration; + private final HashMap<Integer, HashMap<Integer, Margins>> mPositionDirectionMarginMapping; /** */ @Inject public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout, - @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin, + @Named(COMPLICATION_DIRECTIONAL_SPACING_DEFAULT) int defaultDirectionalSpacing, + @Named(COMPLICATION_MARGIN_POSITION_START) int complicationMarginPositionStart, + @Named(COMPLICATION_MARGIN_POSITION_TOP) int complicationMarginPositionTop, + @Named(COMPLICATION_MARGIN_POSITION_END) int complicationMarginPositionEnd, + @Named(COMPLICATION_MARGIN_POSITION_BOTTOM) int complicationMarginPositionBottom, TouchInsetManager.TouchInsetSession session, @Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration, @Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) { mLayout = layout; - mDefaultMargin = defaultMargin; + mDefaultDirectionalSpacing = defaultDirectionalSpacing; mSession = session; mFadeInDuration = fadeInDuration; mFadeOutDuration = fadeOutDuration; + mPositionDirectionMarginMapping = generatePositionDirectionalMarginsMapping( + complicationMarginPositionStart, + complicationMarginPositionTop, + complicationMarginPositionEnd, + complicationMarginPositionBottom); + } + + private static HashMap<Integer, HashMap<Integer, Margins>> + generatePositionDirectionalMarginsMapping(int complicationMarginPositionStart, + int complicationMarginPositionTop, + int complicationMarginPositionEnd, + int complicationMarginPositionBottom) { + HashMap<Integer, HashMap<Integer, Margins>> mapping = new HashMap<>(); + + final Margins startMargins = new Margins(complicationMarginPositionStart, 0, 0, 0); + final Margins topMargins = new Margins(0, complicationMarginPositionTop, 0, 0); + final Margins endMargins = new Margins(0, 0, complicationMarginPositionEnd, 0); + final Margins bottomMargins = new Margins(0, 0, 0, complicationMarginPositionBottom); + + addToMapping(mapping, POSITION_START | POSITION_TOP, DIRECTION_END, topMargins); + addToMapping(mapping, POSITION_START | POSITION_TOP, DIRECTION_DOWN, startMargins); + + addToMapping(mapping, POSITION_START | POSITION_BOTTOM, DIRECTION_END, bottomMargins); + addToMapping(mapping, POSITION_START | POSITION_BOTTOM, DIRECTION_UP, startMargins); + + addToMapping(mapping, POSITION_END | POSITION_TOP, DIRECTION_START, topMargins); + addToMapping(mapping, POSITION_END | POSITION_TOP, DIRECTION_DOWN, endMargins); + + addToMapping(mapping, POSITION_END | POSITION_BOTTOM, DIRECTION_START, bottomMargins); + addToMapping(mapping, POSITION_END | POSITION_BOTTOM, DIRECTION_UP, endMargins); + + return mapping; + } + + private static void addToMapping(HashMap<Integer, HashMap<Integer, Margins>> mapping, + @Position int position, @Direction int direction, Margins margins) { + if (!mapping.containsKey(position)) { + mapping.put(position, new HashMap<>()); + } + mapping.get(position).put(direction, margins); } @Override @@ -537,13 +676,13 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll removeComplication(id); } - final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category) - .setDefaultMargin(mDefaultMargin); + final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category); // Add position group if doesn't already exist final int position = lp.getPosition(); if (!mPositions.containsKey(position)) { - mPositions.put(position, new PositionGroup()); + mPositions.put(position, new PositionGroup(mDefaultDirectionalSpacing, + mPositionDirectionMarginMapping.get(lp.getPosition()))); } // Insert entry into group diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java index 71ba720a9589..42b4efdbadb3 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java +++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java @@ -51,7 +51,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { private static final int FIRST_POSITION = POSITION_TOP; private static final int LAST_POSITION = POSITION_END; - private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF; + private static final int DIRECTIONAL_SPACING_UNSPECIFIED = 0xFFFFFFFF; private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF; @Retention(RetentionPolicy.SOURCE) @@ -80,7 +80,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { private final int mWeight; - private final int mMargin; + private final int mDirectionalSpacing; private final int mConstraint; @@ -113,8 +113,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight) { - this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED, - false); + this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED, + CONSTRAINT_UNSPECIFIED, false); } /** @@ -127,11 +127,12 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * @param weight The weight that should be considered for this view when compared to other * views. This has an impact on the placement of the view but not the rendering of * the view. - * @param margin The margin to apply between complications. + * @param directionalSpacing The spacing to apply between complications. */ public ComplicationLayoutParams(int width, int height, @Position int position, - @Direction int direction, int weight, int margin) { - this(width, height, position, direction, weight, margin, CONSTRAINT_UNSPECIFIED, false); + @Direction int direction, int weight, int directionalSpacing) { + this(width, height, position, direction, weight, directionalSpacing, CONSTRAINT_UNSPECIFIED, + false); } /** @@ -144,14 +145,14 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * @param weight The weight that should be considered for this view when compared to other * views. This has an impact on the placement of the view but not the rendering of * the view. - * @param margin The margin to apply between complications. + * @param directionalSpacing The spacing to apply between complications. * @param constraint The max width or height the complication is allowed to spread, depending on * its direction. For horizontal directions, this would be applied on width, * and for vertical directions, height. */ public ComplicationLayoutParams(int width, int height, @Position int position, - @Direction int direction, int weight, int margin, int constraint) { - this(width, height, position, direction, weight, margin, constraint, false); + @Direction int direction, int weight, int directionalSpacing, int constraint) { + this(width, height, position, direction, weight, directionalSpacing, constraint, false); } /** @@ -172,8 +173,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { */ public ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, boolean snapToGuide) { - this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED, - snapToGuide); + this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED, + CONSTRAINT_UNSPECIFIED, snapToGuide); } /** @@ -186,7 +187,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * @param weight The weight that should be considered for this view when compared to other * views. This has an impact on the placement of the view but not the rendering of * the view. - * @param margin The margin to apply between complications. + * @param directionalSpacing The spacing to apply between complications. * @param constraint The max width or height the complication is allowed to spread, depending on * its direction. For horizontal directions, this would be applied on width, * and for vertical directions, height. @@ -197,7 +198,8 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { * from the end of the parent to the guide. */ public ComplicationLayoutParams(int width, int height, @Position int position, - @Direction int direction, int weight, int margin, int constraint, boolean snapToGuide) { + @Direction int direction, int weight, int directionalSpacing, int constraint, + boolean snapToGuide) { super(width, height); if (!validatePosition(position)) { @@ -213,7 +215,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mWeight = weight; - mMargin = margin; + mDirectionalSpacing = directionalSpacing; mConstraint = constraint; @@ -228,7 +230,7 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { mPosition = source.mPosition; mDirection = source.mDirection; mWeight = source.mWeight; - mMargin = source.mMargin; + mDirectionalSpacing = source.mDirectionalSpacing; mConstraint = source.mConstraint; mSnapToGuide = source.mSnapToGuide; } @@ -300,11 +302,12 @@ public class ComplicationLayoutParams extends ViewGroup.LayoutParams { } /** - * Returns the margin to apply between complications, or the given default if no margin is + * Returns the spacing to apply between complications, or the given default if no spacing is * specified. */ - public int getMargin(int defaultMargin) { - return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin; + public int getDirectionalSpacing(int defaultSpacing) { + return mDirectionalSpacing == DIRECTIONAL_SPACING_UNSPECIFIED + ? defaultSpacing : mDirectionalSpacing; } /** diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java index 1158565b9c37..a7d017dda21e 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java @@ -36,11 +36,20 @@ import javax.inject.Named; @Module public abstract class ComplicationHostViewModule { public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout"; - public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default"; + public static final String COMPLICATION_DIRECTIONAL_SPACING_DEFAULT = + "complication_directional_spacing_default"; public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration"; public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration"; public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout"; public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay"; + public static final String COMPLICATION_MARGIN_POSITION_START = + "complication_margin_position_start"; + public static final String COMPLICATION_MARGIN_POSITION_TOP = + "complication_margin_position_top"; + public static final String COMPLICATION_MARGIN_POSITION_END = + "complication_margin_position_end"; + public static final String COMPLICATION_MARGIN_POSITION_BOTTOM = + "complication_margin_position_bottom"; /** * Generates a {@link ConstraintLayout}, which can host @@ -58,11 +67,35 @@ public abstract class ComplicationHostViewModule { } @Provides - @Named(COMPLICATION_MARGIN_DEFAULT) + @Named(COMPLICATION_DIRECTIONAL_SPACING_DEFAULT) static int providesComplicationPadding(@Main Resources resources) { return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin); } + @Provides + @Named(COMPLICATION_MARGIN_POSITION_START) + static int providesComplicationMarginPositionStart(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_start); + } + + @Provides + @Named(COMPLICATION_MARGIN_POSITION_TOP) + static int providesComplicationMarginPositionTop(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_top); + } + + @Provides + @Named(COMPLICATION_MARGIN_POSITION_END) + static int providesComplicationMarginPositionEnd(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_end); + } + + @Provides + @Named(COMPLICATION_MARGIN_POSITION_BOTTOM) + static int providesComplicationMarginPositionBottom(@Main Resources resources) { + return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_bottom); + } + /** * Provides the fade out duration for complications. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index d5a41460c89e..f68bd49230d9 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -23,8 +23,6 @@ import android.content.Context; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import androidx.annotation.Nullable; - import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardViewController; import com.android.systemui.battery.BatterySaverModule; @@ -74,12 +72,12 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.volume.dagger.VolumeModule; -import javax.inject.Named; - import dagger.Binds; import dagger.Module; import dagger.Provides; +import javax.inject.Named; + /** * A dagger module for injecting default implementations of components of System UI. * @@ -115,9 +113,8 @@ public abstract class ReferenceSystemUIModule { @SysUISingleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) - @Nullable static String provideLeakReportEmail() { - return null; + return ""; } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 0d3503e5765c..07efbfef732b 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -217,7 +217,7 @@ public class DozeSensors { true /* settingDef */, true /* configured */, DozeLog.REASON_SENSOR_TAP, - false /* reports touch coordinates */, + true /* reports touch coordinates */, true /* touchscreen */, false /* ignoresSetting */, dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index b70960832d32..85272a609588 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -323,9 +323,7 @@ public class DozeTriggers implements DozeMachine.Part { return; } if (isDoubleTap || isTap) { - if (screenX != -1 && screenY != -1) { - mDozeHost.onSlpiTap(screenX, screenY); - } + mDozeHost.onSlpiTap(screenX, screenY); gentleWakeUp(pulseReason); } else if (isPickup) { if (shouldDropPickupEvent()) { diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a4d4a9a9bf56..09601970e96a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -201,7 +201,7 @@ object Flags { // TODO(b/267722622): Tracking Bug @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = - releasedFlag( + unreleasedFlag( 229, "wallpaper_picker_ui_for_aiwp" ) @@ -416,12 +416,6 @@ object Flags { // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") - @JvmField - // TODO(b/271428141): Tracking Bug - val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = releasedFlag( - 1004, - "enable_low_light_clock_undocked") - // TODO(b/273509374): Tracking Bug @JvmField val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS = releasedFlag(1006, @@ -710,6 +704,5 @@ object Flags { // TODO(b/278761837): Tracking Bug @JvmField - val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter", - teamfood = true) + val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter") } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt index 6bc763ce421e..d3678b5a2a4e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt @@ -87,7 +87,7 @@ class KeyboardBacklightDialog( override fun onCreate(savedInstanceState: Bundle?) { setUpWindowProperties(this) - setWindowTitle() + setWindowPosition() updateResources() rootView = buildRootView() setContentView(rootView) @@ -233,24 +233,29 @@ class KeyboardBacklightDialog( } } - private fun setWindowTitle() { - val attrs = window.attributes - attrs.title = "KeyboardBacklightDialog" - attrs?.y = dialogBottomMargin - window.attributes = attrs + private fun setWindowPosition() { + window?.apply { + setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + this.attributes = + WindowManager.LayoutParams().apply { + copyFrom(attributes) + y = dialogBottomMargin + } + } } private fun setUpWindowProperties(dialog: Dialog) { - val window = dialog.window - window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar - window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) - window.addFlags( - WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - ) - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) - window.setBackgroundDrawableResource(android.R.color.transparent) - window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL) + dialog.window?.apply { + requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar + setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL) + addFlags( + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + ) + clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) + setBackgroundDrawableResource(android.R.color.transparent) + attributes.title = "KeyboardBacklightDialog" + } setCanceledOnTouchOutside(true) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 9e208fd60fd7..0a2d05e80edb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -726,6 +726,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } } + + @Override + public void onStrongAuthStateChanged(int userId) { + if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { + doKeyguardLocked(null); + } + } }; ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() { @@ -1949,8 +1956,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * Enable the keyguard if the settings are appropriate. */ private void doKeyguardLocked(Bundle options) { - // if another app is disabling us, don't show - if (!mExternallyEnabled) { + // if another app is disabling us, don't show unless we're in lockdown mode + if (!mExternallyEnabled + && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) { if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled"); mNeedToReshowWhenReenabled = true; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 5b71a2ed1991..9621f03f63a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -45,7 +45,6 @@ import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState -import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.log.SessionTracker import com.android.systemui.log.table.TableLogBuffer @@ -239,9 +238,7 @@ constructor( // Clear auth status when keyguard is going away or when the user is switching or device // starts going to sleep. merge( - keyguardRepository.wakefulness.map { - WakefulnessModel.isSleepingOrStartingToSleep(it) - }, + keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }, keyguardRepository.isKeyguardGoingAway, userRepository.userSwitchingInProgress ) @@ -315,9 +312,7 @@ constructor( tableLogBuffer ), logAndObserve( - keyguardRepository.wakefulness - .map { WakefulnessModel.isSleepingOrStartingToSleep(it) } - .isFalse(), + keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(), "deviceNotSleepingOrNotStartingToSleep", tableLogBuffer ), 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 0b506cfa4716..7c14280a7858 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 @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.repository import android.os.Build import android.util.Log -import com.android.keyguard.ViewMediatorCallback import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN @@ -104,7 +103,6 @@ interface KeyguardBouncerRepository { class KeyguardBouncerRepositoryImpl @Inject constructor( - private val viewMediatorCallback: ViewMediatorCallback, private val clock: SystemClock, @Application private val applicationScope: CoroutineScope, @BouncerLog private val buffer: TableLogBuffer, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 3567d814f63e..742e53515e82 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -26,7 +26,6 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.doze.DozeHost import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener @@ -105,7 +104,7 @@ interface KeyguardRepository { * returns `false`. In order to account for that, observers should also use the * [linearDozeAmount] flow to check if it's greater than `0` */ - val isDozing: Flow<Boolean> + val isDozing: StateFlow<Boolean> /** * Observable for whether the device is dreaming. @@ -133,6 +132,8 @@ interface KeyguardRepository { /** Doze state information, as it transitions */ val dozeTransitionModel: Flow<DozeTransitionModel> + val lastDozeTapToWakePosition: StateFlow<Point?> + /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> @@ -181,6 +182,10 @@ interface KeyguardRepository { /** Sets whether quick settings or quick-quick settings is visible. */ fun setQuickSettingsVisible(isVisible: Boolean) + + fun setLastDozeTapToWakePosition(position: Point) + + fun setIsDozing(isDozing: Boolean) } /** Encapsulates application state for the keyguard. */ @@ -189,7 +194,6 @@ class KeyguardRepositoryImpl @Inject constructor( statusBarStateController: StatusBarStateController, - dozeHost: DozeHost, wakefulnessLifecycle: WakefulnessLifecycle, biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, @@ -333,24 +337,19 @@ constructor( awaitClose { keyguardStateController.removeCallback(callback) } } - override val isDozing: Flow<Boolean> = - conflatedCallbackFlow { - val callback = - object : DozeHost.Callback { - override fun onDozingChanged(isDozing: Boolean) { - trySendWithFailureLogging(isDozing, TAG, "updated isDozing") - } - } - dozeHost.addCallback(callback) - trySendWithFailureLogging( - statusBarStateController.isDozing, - TAG, - "initial isDozing", - ) + private val _isDozing = MutableStateFlow(statusBarStateController.isDozing) + override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow() - awaitClose { dozeHost.removeCallback(callback) } - } - .distinctUntilChanged() + override fun setIsDozing(isDozing: Boolean) { + _isDozing.value = isDozing + } + + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) + override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() + + override fun setLastDozeTapToWakePosition(position: Point) { + _lastDozeTapToWakePosition.value = position + } override val isDreamingWithOverlay: Flow<Boolean> = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt index a17481a9978b..482e9a3d09d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt @@ -24,7 +24,6 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource -import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LightRevealEffect @@ -43,7 +42,7 @@ val DEFAULT_REVEAL_EFFECT = LiftReveal /** * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen - * contents during transitions between AOD and lockscreen/unlocked. + * contents during transitions between DOZE or AOD and lockscreen/unlocked. */ interface LightRevealScrimRepository { @@ -64,13 +63,20 @@ constructor( ) : LightRevealScrimRepository { /** The reveal effect used if the device was locked/unlocked via the power button. */ - private val powerButtonReveal = - PowerButtonReveal( - context.resources - .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y) - .toFloat() + private val powerButtonRevealEffect: Flow<LightRevealEffect?> = + flowOf( + PowerButtonReveal( + context.resources + .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y) + .toFloat() + ) ) + private val tapRevealEffect: Flow<LightRevealEffect?> = + keyguardRepository.lastDozeTapToWakePosition.map { + it?.let { constructCircleRevealFromPoint(it) } + } + /** * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint * sensor location on the screen (in pixels) changes due to configuration changes. @@ -102,18 +108,11 @@ constructor( /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = - keyguardRepository.wakefulness.map { wakefulnessModel -> - val wakingUpFromPowerButton = - wakefulnessModel.isWakingUpOrAwake && - wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON - val sleepingFromPowerButton = - !wakefulnessModel.isWakingUpOrAwake && - wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON - - if (wakingUpFromPowerButton || sleepingFromPowerButton) { - powerButtonReveal - } else { - LiftReveal + keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel -> + when { + wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect + wakefulnessModel.isAwakeFromTap() -> tapRevealEffect + else -> flowOf(LiftReveal) } } 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 1fa018bcbf39..e4906696a5e3 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.ChannelExt.trySendWithFailureLoggin 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.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn /** Encapsulates any state relevant to trust agents and trust grants. */ interface TrustRepository { @@ -45,6 +47,9 @@ interface TrustRepository { /** Flow representing whether active unlock is available for the current user. */ val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> + + /** Reports that whether trust is managed has changed for the current user. */ + val isCurrentUserTrustManaged: StateFlow<Boolean> } @SysUISingleton @@ -57,6 +62,7 @@ constructor( private val logger: TrustRepositoryLogger, ) : TrustRepository { private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>() + private val trustManagedForUser = mutableMapOf<Int, TrustManagedModel>() private val trust = conflatedCallbackFlow { @@ -79,9 +85,16 @@ constructor( override fun onTrustError(message: CharSequence?) = Unit - override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit - override fun onEnabledTrustAgentsChanged(userId: Int) = Unit + + override fun onTrustManagedChanged(isTrustManaged: Boolean, userId: Int) { + logger.onTrustManagedChanged(isTrustManaged, userId) + trySendWithFailureLogging( + TrustManagedModel(userId, isTrustManaged), + TrustRepositoryLogger.TAG, + "onTrustManagedChanged" + ) + } } trustManager.registerTrustListener(callback) logger.trustListenerRegistered() @@ -91,18 +104,43 @@ constructor( } } .onEach { - latestTrustModelForUser[it.userId] = it - logger.trustModelEmitted(it) + when (it) { + is TrustModel -> { + latestTrustModelForUser[it.userId] = it + logger.trustModelEmitted(it) + } + is TrustManagedModel -> { + trustManagedForUser[it.userId] = it + logger.trustManagedModelEmitted(it) + } + } } .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1) - override val isCurrentUserTrusted: Flow<Boolean> = - combine(trust, userRepository.selectedUserInfo, ::Pair) - .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false } - .distinctUntilChanged() - .onEach { logger.isCurrentUserTrusted(it) } - .onStart { emit(false) } - // TODO: Implement based on TrustManager callback b/267322286 override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true) + + override val isCurrentUserTrustManaged: StateFlow<Boolean> + get() = + combine(trust, userRepository.selectedUserInfo, ::Pair) + .map { isUserTrustManaged(it.second.id) } + .distinctUntilChanged() + .onEach { logger.isCurrentUserTrustManaged(it) } + .onStart { emit(false) } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false + ) + + private fun isUserTrustManaged(userId: Int) = + trustManagedForUser[userId]?.isTrustManaged ?: false + + override val isCurrentUserTrusted: Flow<Boolean> + get() = + combine(trust, userRepository.selectedUserInfo, ::Pair) + .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false } + .distinctUntilChanged() + .onEach { logger.isCurrentUserTrusted(it) } + .onStart { emit(false) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt new file mode 100644 index 000000000000..2efcd0c1ffe7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt @@ -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.domain.interactor + +import android.graphics.Point +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import javax.inject.Inject + +@SysUISingleton +class DozeInteractor +@Inject +constructor( + private val keyguardRepository: KeyguardRepository, +) { + + fun setIsDozing(isDozing: Boolean) { + keyguardRepository.setIsDozing(isDozing) + } + + fun setLastTapToWakePosition(position: Point) { + keyguardRepository.setLastDozeTapToWakePosition(position) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index cde67f979132..38eaccee7284 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -24,6 +24,8 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.kotlin.Utils.Companion.toQuad +import com.android.systemui.util.kotlin.Utils.Companion.toQuint import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index aca40195dbcf..323fc317ebe1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -24,13 +24,11 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo -import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch @SysUISingleton @@ -54,7 +52,7 @@ constructor( .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair) .collect { (wakefulnessModel, lastStartedTransition) -> if ( - isWakingOrStartingToWake(wakefulnessModel) && + wakefulnessModel.isStartingToWake() && lastStartedTransition.to == KeyguardState.DOZING ) { keyguardTransitionRepository.startTransition( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index fc7bfb4e45f4..36c8eb186924 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -26,13 +26,13 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 39c630b1fa6f..cfcb65471d8b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 0505d37262b0..b5e289f2ac6e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -27,6 +27,8 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.util.kotlin.Utils.Companion.toQuad +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject @@ -144,7 +146,7 @@ constructor( keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardInteractor.statusBarState, keyguardInteractor.isKeyguardUnlocked, - ::toTriple + ::Triple ), ::toQuad ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index 47846d1f4118..b0dbc591dd15 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -24,12 +24,12 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index bc55bd4b8c92..da09e1f0f4d2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -27,6 +27,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration @@ -60,7 +61,7 @@ constructor( keyguardInteractor.wakefulnessModel, keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardInteractor.isKeyguardOccluded, - ::toTriple + ::Triple ), ::toQuad ) @@ -100,7 +101,7 @@ constructor( keyguardInteractor.wakefulnessModel, keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardInteractor.isAodAvailable, - ::toTriple + ::Triple ), ::toQuad ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 1ac0c526f975..3cf9a9ec5a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -33,7 +33,6 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel -import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -108,18 +107,12 @@ constructor( */ val isAbleToDream: Flow<Boolean> = merge(isDreaming, isDreamingWithOverlay) - .combine( - dozeTransitionModel, - { isDreaming, dozeTransitionModel -> - isDreaming && isDozeOff(dozeTransitionModel.to) - } - ) - .sample( - wakefulnessModel, - { isAbleToDream, wakefulnessModel -> - isAbleToDream && isWakingOrStartingToWake(wakefulnessModel) - } - ) + .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel -> + isDreaming && isDozeOff(dozeTransitionModel.to) + } + .sample(wakefulnessModel) { isAbleToDream, wakefulnessModel -> + isAbleToDream && wakefulnessModel.isStartingToWake() + } .flatMapLatest { isAbleToDream -> flow { delay(50) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index e3e3527e851f..b7dd1a5f42b5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -27,29 +27,5 @@ package com.android.systemui.keyguard.domain.interactor * 'when' clause of [KeyguardTransitionCoreStartable] */ sealed class TransitionInteractor(val name: String) { - abstract fun start() - - fun <A, B, C> toTriple(a: A, b: B, c: C) = Triple(a, b, c) - - fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) - - fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c) - - fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) - - fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = Quad(a, bcd.first, bcd.second, bcd.third) - - fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) = - Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth) } - -data class Quad<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D) - -data class Quint<A, B, C, D, E>( - val first: A, - val second: B, - val third: C, - val fourth: D, - val fifth: E -) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt index 4fd14b1ce087..cdfab1a90b66 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt @@ -16,10 +16,18 @@ package com.android.systemui.keyguard.shared.model +sealed class TrustMessage + /** Represents the trust state */ data class TrustModel( /** If true, the system believes the environment to be trusted. */ val isTrusted: Boolean, /** The user, for which the trust changed. */ val userId: Int, -) +) : TrustMessage() + +/** Represents where trust agents are enabled for a particular user. */ +data class TrustManagedModel( + val userId: Int, + val isTrustManaged: Boolean, +) : TrustMessage() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt index b32597d5cff0..51ce7ff45182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt @@ -23,6 +23,9 @@ enum class WakeSleepReason { /** The physical power button was pressed to wake up or sleep the device. */ POWER_BUTTON, + /** The user has taped or double tapped to wake the screen */ + TAP, + /** Something else happened to wake up or sleep the device. */ OTHER; @@ -30,6 +33,7 @@ enum class WakeSleepReason { fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason { return when (reason) { PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON + PowerManager.WAKE_REASON_TAP -> TAP else -> OTHER } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt index 03dee0045c10..7ca90ba63fda 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -20,26 +20,31 @@ import com.android.systemui.keyguard.WakefulnessLifecycle /** Model device wakefulness states. */ data class WakefulnessModel( val state: WakefulnessState, - val isWakingUpOrAwake: Boolean, val lastWakeReason: WakeSleepReason, val lastSleepReason: WakeSleepReason, ) { - companion object { - fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { - return model.state == WakefulnessState.ASLEEP || - model.state == WakefulnessState.STARTING_TO_SLEEP - } + fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE - fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { - return model.state == WakefulnessState.AWAKE || - model.state == WakefulnessState.STARTING_TO_WAKE - } + fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP + + fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP + + fun isStartingToSleepFromPowerButton() = + isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON + fun isWakingFromPowerButton() = + isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON + + fun isTransitioningFromPowerButton() = + isStartingToSleepFromPowerButton() || isWakingFromPowerButton() + + fun isAwakeFromTap() = + state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP + + companion object { fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel { return WakefulnessModel( WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness), - wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING || - wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE, WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason), WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason), ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 385e72017bae..2469a98140e3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -42,7 +42,9 @@ import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; @@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final Handler mHandler; private final Intent mIntent; private final UserHandle mUser; + private final DelayableExecutor mExecutor; private final IBinder mToken = new Binder(); private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; @@ -100,25 +103,27 @@ public class TileLifecycleManager extends BroadcastReceiver implements private int mBindTryCount; private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY; - private boolean mBound; + private AtomicBoolean mBound = new AtomicBoolean(false); private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false); private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false); - private boolean mUnbindImmediate; + private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false); @Nullable private TileChangeListener mChangeListener; // Return value from bindServiceAsUser, determines whether safe to call unbind. - private boolean mIsBound; + private AtomicBoolean mIsBound = new AtomicBoolean(false); @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, - @Assisted Intent intent, @Assisted UserHandle user) { + @Assisted Intent intent, @Assisted UserHandle user, + @Background DelayableExecutor executor) { mContext = context; mHandler = handler; mIntent = intent; mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder()); mIntent.putExtra(TileService.EXTRA_TOKEN, mToken); mUser = user; + mExecutor = executor; mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser); @@ -184,22 +189,21 @@ public class TileLifecycleManager extends BroadcastReceiver implements * Binds just long enough to send any queued messages, then unbinds. */ public void flushMessagesAndUnbind() { - mUnbindImmediate = true; - setBindService(true); + mExecutor.execute(() -> { + mUnbindImmediate.set(true); + setBindService(true); + }); } - /** - * Binds or unbinds to IQSService - */ @WorkerThread - public void setBindService(boolean bind) { - if (mBound && mUnbindImmediate) { + private void setBindService(boolean bind) { + if (mBound.get() && mUnbindImmediate.get()) { // If we are already bound and expecting to unbind, this means we should stay bound // because something else wants to hold the connection open. - mUnbindImmediate = false; + mUnbindImmediate.set(false); return; } - mBound = bind; + mBound.set(bind); if (bind) { if (mBindTryCount == MAX_BIND_RETRIES) { // Too many failures, give up on this tile until an update. @@ -212,31 +216,38 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser); mBindTryCount++; try { - mIsBound = bindServices(); - if (!mIsBound) { + mIsBound.set(bindServices()); + if (!mIsBound.get()) { mContext.unbindService(this); } } catch (SecurityException e) { Log.e(TAG, "Failed to bind to service", e); - mIsBound = false; + mIsBound.set(false); } } else { if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser); // Give it another chance next time it needs to be bound, out of kindness. mBindTryCount = 0; freeWrapper(); - if (mIsBound) { + if (mIsBound.get()) { try { mContext.unbindService(this); } catch (Exception e) { Log.e(TAG, "Failed to unbind service " + mIntent.getComponent().flattenToShortString(), e); } - mIsBound = false; + mIsBound.set(false); } } } + /** + * Binds or unbinds to IQSService + */ + public void executeSetBindService(boolean bind) { + mExecutor.execute(() -> setBindService(bind)); + } + private boolean bindServices() { String packageName = mIntent.getComponent().getPackageName(); if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName, @@ -317,10 +328,12 @@ public class TileLifecycleManager extends BroadcastReceiver implements } onTileRemoved(); } - if (mUnbindImmediate) { - mUnbindImmediate = false; - setBindService(false); - } + mExecutor.execute(() -> { + if (mUnbindImmediate.get()) { + mUnbindImmediate.set(false); + setBindService(false); + } + }); } public void handleDestroy() { @@ -335,19 +348,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements if (mWrapper == null) return; freeWrapper(); // Clearly not bound anymore - mIsBound = false; - if (!mBound) return; + mIsBound.set(false); + if (!mBound.get()) return; if (DEBUG) Log.d(TAG, "handleDeath"); if (checkComponentState()) { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - if (mBound) { - // Retry binding. - setBindService(true); - } - } - }, mBindRetryDelay); + mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay); } } @@ -410,11 +415,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements mChangeListener.onTileChanged(mIntent.getComponent()); } stopPackageListening(); - if (mBound) { - // Trying to bind again will check the state of the package before bothering to bind. - if (DEBUG) Log.d(TAG, "Trying to rebind"); - setBindService(true); - } + mExecutor.execute(() -> { + if (mBound.get()) { + // Trying to bind again will check the state of the package before bothering to + // bind. + if (DEBUG) Log.d(TAG, "Trying to rebind"); + setBindService(true); + } + + }); } private boolean isComponentAvailable() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index 7a10a27f6aca..941a9d6dc952 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -35,6 +35,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; import java.util.Objects; @@ -75,12 +76,12 @@ public class TileServiceManager { TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { this(tileServices, handler, userTracker, customTileAddedRepository, new TileLifecycleManager(handler, tileServices.getContext(), tileServices, - new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, - new Intent(TileService.ACTION_QS_TILE).setComponent(component), - userTracker.getUserHandle())); + new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher, + new Intent(TileService.ACTION_QS_TILE).setComponent(component), + userTracker.getUserHandle(), executor)); } @VisibleForTesting @@ -203,7 +204,7 @@ public class TileServiceManager { mBound = true; mJustBound = true; mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); } private void unbindService() { @@ -213,7 +214,7 @@ public class TileServiceManager { } mBound = false; mJustBound = false; - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); } public void calculateBindPriority(long currentTime) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 121955cced1a..c8691acfd278 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; @@ -47,6 +48,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.Collections; @@ -79,6 +81,7 @@ public class TileServices extends IQSService.Stub { private final StatusBarIconController mStatusBarIconController; private final PanelInteractor mPanelInteractor; private final CustomTileAddedRepository mCustomTileAddedRepository; + private final DelayableExecutor mBackgroundExecutor; private int mMaxBound = DEFAULT_MAX_BOUND; @@ -92,7 +95,8 @@ public class TileServices extends IQSService.Stub { CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, + @Background DelayableExecutor backgroundExecutor) { mHost = host; mKeyguardStateController = keyguardStateController; mContext = mHost.getContext(); @@ -105,6 +109,7 @@ public class TileServices extends IQSService.Stub { mCommandQueue.addCallback(mRequestListeningCallback); mPanelInteractor = panelInteractor; mCustomTileAddedRepository = customTileAddedRepository; + mBackgroundExecutor = backgroundExecutor; } public Context getContext() { @@ -132,7 +137,7 @@ public class TileServices extends IQSService.Stub { protected TileServiceManager onCreateTileService(ComponentName component, BroadcastDispatcher broadcastDispatcher) { return new TileServiceManager(this, mHandlerProvider.get(), component, - broadcastDispatcher, mUserTracker, mCustomTileAddedRepository); + broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor); } public void freeService(CustomTile tile, TileServiceManager service) { @@ -339,9 +344,26 @@ public class TileServices extends IQSService.Stub { verifyCaller(customTile); return customTile.getQsTile(); } + Log.e(TAG, "Tile for token " + token + "not found. " + + "Tiles in map: " + availableTileComponents()); return null; } + private String availableTileComponents() { + StringBuilder sb = new StringBuilder("["); + synchronized (mServices) { + mTokenMap.forEach((iBinder, customTile) -> + sb.append(iBinder.toString()) + .append(":") + .append(customTile.getComponent().flattenToShortString()) + .append(":") + .append(customTile.getUser()) + .append(",")); + } + sb.append("]"); + return sb.toString(); + } + @Override public void startUnlockAndRun(IBinder token) { CustomTile customTile = getTileForToken(token); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index e026bdbc2e08..544e6ad295ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static android.graphics.drawable.Icon.TYPE_URI; import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT; import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE; @@ -225,7 +226,12 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { return; } mSelectedCard = cards.get(selectedIndex); - mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage(); + if (cardImageIcon.getType() == TYPE_URI) { + mCardViewDrawable = null; + } else { + mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext); + } refreshState(); } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt new file mode 100644 index 000000000000..d0769ebe941e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneContainerConfig.kt @@ -0,0 +1,46 @@ +/* + * 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.model + +import com.android.systemui.scene.shared.model.SceneKey + +/** Models the configuration of a single scene container. */ +data class SceneContainerConfig( + /** Container name. Must be unique across all containers in System UI. */ + val name: String, + + /** + * The keys to all scenes in the container, sorted by z-order such that the last one renders on + * top of all previous ones. Scene keys within the same container must not repeat but it's okay + * to have the same scene keys in different containers. + */ + val sceneKeys: List<SceneKey>, + + /** + * The key of the scene that is the initial current scene when the container is first set up, + * before taking any application state in to account. + */ + val initialSceneKey: SceneKey, +) { + init { + check(sceneKeys.isNotEmpty()) { "A container must have at least one scene key." } + + check(sceneKeys.contains(initialSceneKey)) { + "The initial key \"$initialSceneKey\" is not present in this container." + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt new file mode 100644 index 000000000000..61b162b014d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -0,0 +1,136 @@ +/* + * 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 +import com.android.systemui.scene.shared.model.SceneModel +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** Source of truth for scene framework application state. */ +class SceneContainerRepository +@Inject +constructor( + containerConfigurations: Set<SceneContainerConfig>, +) { + + private val containerConfigByName: Map<String, SceneContainerConfig> = + containerConfigurations.associateBy { config -> config.name } + private val containerVisibilityByName: Map<String, MutableStateFlow<Boolean>> = + containerConfigByName + .map { (containerName, _) -> containerName to MutableStateFlow(true) } + .toMap() + private val currentSceneByContainerName: Map<String, MutableStateFlow<SceneModel>> = + containerConfigByName + .map { (containerName, config) -> + containerName to MutableStateFlow(SceneModel(config.initialSceneKey)) + } + .toMap() + private val sceneTransitionProgressByContainerName: Map<String, MutableStateFlow<Float>> = + containerConfigByName + .map { (containerName, _) -> containerName to MutableStateFlow(1f) } + .toMap() + + init { + val repeatedContainerNames = + containerConfigurations + .groupingBy { config -> config.name } + .eachCount() + .filter { (_, count) -> count > 1 } + check(repeatedContainerNames.isEmpty()) { + "Container names must be unique. The following container names appear more than once: ${ + repeatedContainerNames + .map { (name, count) -> "\"$name\" appears $count times" } + .joinToString(", ") + }" + } + } + + /** + * Returns the keys to all scenes in the container with the given name. + * + * The scenes will be sorted in z-order such that the last one is the one that should be + * rendered on top of all previous ones. + */ + fun allSceneKeys(containerName: String): List<SceneKey> { + return containerConfigByName[containerName]?.sceneKeys + ?: error(noSuchContainerErrorMessage(containerName)) + } + + /** Sets the current scene in the container with the given name. */ + fun setCurrentScene(containerName: String, scene: SceneModel) { + check(allSceneKeys(containerName).contains(scene.key)) { + """ + Cannot set current scene key to "${scene.key}". The container "$containerName" does + not contain a scene with that key. + """ + .trimIndent() + } + + currentSceneByContainerName.setValue(containerName, scene) + } + + /** The current scene in the container with the given name. */ + fun currentScene(containerName: String): StateFlow<SceneModel> { + return currentSceneByContainerName.mutableOrError(containerName).asStateFlow() + } + + /** Sets whether the container with the given name is visible. */ + fun setVisible(containerName: String, isVisible: Boolean) { + containerVisibilityByName.setValue(containerName, isVisible) + } + + /** Whether the container with the given name should be visible. */ + fun isVisible(containerName: String): StateFlow<Boolean> { + return containerVisibilityByName.mutableOrError(containerName).asStateFlow() + } + + /** Sets scene transition progress to the current scene in the container with the given name. */ + fun setSceneTransitionProgress(containerName: String, progress: Float) { + sceneTransitionProgressByContainerName.setValue(containerName, progress) + } + + /** Progress of the transition into the current scene in the container with the given name. */ + fun sceneTransitionProgress(containerName: String): StateFlow<Float> { + return sceneTransitionProgressByContainerName.mutableOrError(containerName).asStateFlow() + } + + private fun <T> Map<String, MutableStateFlow<T>>.mutableOrError( + containerName: String, + ): MutableStateFlow<T> { + return this[containerName] ?: error(noSuchContainerErrorMessage(containerName)) + } + + private fun <T> Map<String, MutableStateFlow<T>>.setValue( + containerName: String, + value: T, + ) { + val mutable = mutableOrError(containerName) + mutable.value = value + } + + private fun noSuchContainerErrorMessage(containerName: String): String { + return """ + No container named "$containerName". Existing containers: + ${containerConfigByName.values.joinToString(", ") { it.name }} + """ + .trimIndent() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt new file mode 100644 index 000000000000..1e55975658a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -0,0 +1,73 @@ +/* + * 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.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.data.repository.SceneContainerRepository +import com.android.systemui.scene.shared.model.SceneKey +import com.android.systemui.scene.shared.model.SceneModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +/** Business logic and app state accessors for the scene framework. */ +@SysUISingleton +class SceneInteractor +@Inject +constructor( + private val repository: SceneContainerRepository, +) { + + /** + * Returns the keys of all scenes in the container with the given name. + * + * The scenes will be sorted in z-order such that the last one is the one that should be + * rendered on top of all previous ones. + */ + fun allSceneKeys(containerName: String): List<SceneKey> { + return repository.allSceneKeys(containerName) + } + + /** Sets the scene in the container with the given name. */ + fun setCurrentScene(containerName: String, scene: SceneModel) { + repository.setCurrentScene(containerName, scene) + } + + /** The current scene in the container with the given name. */ + fun currentScene(containerName: String): StateFlow<SceneModel> { + return repository.currentScene(containerName) + } + + /** Sets the visibility of the container with the given name. */ + fun setVisible(containerName: String, isVisible: Boolean) { + return repository.setVisible(containerName, isVisible) + } + + /** Whether the container with the given name is visible. */ + fun isVisible(containerName: String): StateFlow<Boolean> { + return repository.isVisible(containerName) + } + + /** Sets scene transition progress to the current scene in the container with the given name. */ + fun setSceneTransitionProgress(containerName: String, progress: Float) { + repository.setSceneTransitionProgress(containerName, progress) + } + + /** Progress of the transition into the current scene in the container with the given name. */ + fun sceneTransitionProgress(containerName: String): StateFlow<Float> { + return repository.sceneTransitionProgress(containerName) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt new file mode 100644 index 000000000000..435ff4baffd8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt @@ -0,0 +1,87 @@ +/* + * 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.model + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Defines interface for classes that can describe a "scene". + * + * In the scene framework, there can be multiple scenes in a single scene "container". The container + * takes care of rendering the current scene and allowing scenes to be switched from one to another + * based on either user action (for example, swiping down while on the lock screen scene may switch + * to the shade scene). + * + * The framework also supports multiple containers, each one with its own configuration. + */ +interface Scene { + + /** Uniquely-identifying key for this scene. The key must be unique within its container. */ + val key: SceneKey + + /** + * Returns a mapping between [UserAction] and flows that emit a [SceneModel]. + * + * When the scene framework detects the user action, it starts a transition to the scene + * described by the latest value in the flow that's mapped from that user action. + * + * Once the [Scene] becomes the current one, the scene framework will invoke this method and set + * up collectors to watch for new values emitted to each of the flows. If a value is added to + * the map at a given [UserAction], the framework will set up user input handling for that + * [UserAction] and, if such a user action is detected, the framework will initiate a transition + * to that [SceneModel]. + * + * Note that calling this method does _not_ mean that the given user action has occurred. + * Instead, the method is called before any user action/gesture is detected so that the + * framework can decide whether to set up gesture/input detectors/listeners for that type of + * user action. + * + * Note that a missing value for a specific [UserAction] means that the user action of the given + * type is not currently active in the scene and should be ignored by the framework, while the + * current scene is this one. + * + * The API is designed such that it's possible to emit ever-changing values for each + * [UserAction] to enable, disable, or change the destination scene of a given user action. + */ + fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> = + MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow() +} + +/** Enumerates all scene framework supported user actions. */ +sealed interface UserAction { + + /** The user is scrolling, dragging, swiping, or flinging. */ + data class Swipe( + /** The direction of the swipe. */ + val direction: Direction, + /** The number of pointers that were used (for example, one or two fingers). */ + val pointerCount: Int = 1, + ) : UserAction + + /** The user has hit the back button or performed the back navigation gesture. */ + object Back : UserAction +} + +/** Enumerates all known "cardinal" directions for user actions. */ +enum class Direction { + LEFT, + UP, + RIGHT, + DOWN, +} 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 new file mode 100644 index 000000000000..9ef439d72118 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt @@ -0,0 +1,49 @@ +/* + * 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.model + +/** Keys of all known scenes. */ +sealed class SceneKey( + private val loggingName: String, +) { + /** + * The bouncer is the scene that displays authentication challenges like PIN, password, or + * pattern. + */ + object Bouncer : SceneKey("bouncer") + + /** + * "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any + * content from the scene framework. + */ + object Gone : SceneKey("gone") + + /** The lock screen 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. + */ + object Shade : SceneKey("shade") + + /** The quick settings scene shows the quick setting tiles. */ + object QuickSettings : SceneKey("quick_settings") + + override fun toString(): String { + return loggingName + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt new file mode 100644 index 000000000000..f3d549f03868 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneModel.kt @@ -0,0 +1,27 @@ +/* + * 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.model + +/** Models a scene. */ +data class SceneModel( + + /** The key of the scene. */ + val key: SceneKey, + + /** An optional name for the transition that led to this scene being the current scene. */ + val transitionName: String? = null, +) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt new file mode 100644 index 000000000000..afc053151ab5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -0,0 +1,64 @@ +/* + * 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.viewmodel + +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 dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.StateFlow + +/** Models UI state for a single scene container. */ +class SceneContainerViewModel +@AssistedInject +constructor( + private val interactor: SceneInteractor, + @Assisted private val containerName: String, +) { + /** + * Keys of all scenes in the container. + * + * The scenes will be sorted in z-order such that the last one is the one that should be + * rendered on top of all previous ones. + */ + val allSceneKeys: List<SceneKey> = interactor.allSceneKeys(containerName) + + /** The current scene. */ + val currentScene: StateFlow<SceneModel> = interactor.currentScene(containerName) + + /** Whether the container is visible. */ + val isVisible: StateFlow<Boolean> = interactor.isVisible(containerName) + + /** Requests a transition to the scene with the given key. */ + fun setCurrentScene(scene: SceneModel) { + interactor.setCurrentScene(containerName, scene) + } + + /** Notifies of the progress of a scene transition. */ + fun setSceneTransitionProgress(progress: Float) { + interactor.setSceneTransitionProgress(containerName, progress) + } + + @AssistedFactory + interface Factory { + fun create( + containerName: String, + ): SceneContainerViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 926ede99f5a9..af12bc2ca9f8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -4373,7 +4373,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean shouldHeadsUpBeVisible() { - return mHeadsUpAppearanceController.shouldBeVisible(); + return mHeadsUpAppearanceController != null && + mHeadsUpAppearanceController.shouldBeVisible(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java deleted file mode 100644 index 5ad2ba9d6f5f..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.SOURCE) -public @interface ShadeViewRefactor { - /** - * Returns the refactor component. - * @return the refactor component. - */ - RefactorComponent value(); - - public enum RefactorComponent { - ADAPTER, - LAYOUT_ALGORITHM, - STATE_RESOLVER, - DECORATOR, - INPUT, - COORDINATOR, - SHADE_VIEW - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java index 9a33a9440602..2d0395a2f606 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java @@ -27,7 +27,6 @@ import android.view.View; import android.view.animation.Interpolator; import com.android.app.animation.Interpolators; -import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableView; /** @@ -90,7 +89,6 @@ public class NotificationSection { } - @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER) private void startTopAnimation(boolean animate) { int previousEndValue = mEndAnimationRect.top; int newEndValue = mBounds.top; @@ -139,7 +137,6 @@ public class NotificationSection { mTopAnimator = animator; } - @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER) private void startBottomAnimation(boolean animate) { int previousStartValue = mStartAnimationRect.bottom; int previousEndValue = mEndAnimationRect.bottom; @@ -188,13 +185,11 @@ public class NotificationSection { mBottomAnimator = animator; } - @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW) private void setBackgroundTop(int top) { mCurrentBounds.top = top; mOwningView.invalidate(); } - @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW) private void setBackgroundBottom(int bottom) { mCurrentBounds.bottom = bottom; mOwningView.invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index cf051fbc2504..b81cb2be1c4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -101,8 +101,6 @@ import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.notification.ShadeViewRefactor; -import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -679,7 +677,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onFinishInflate() { super.onFinishInflate(); @@ -740,7 +737,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooter() { if (mFooterView == null) { return; @@ -773,12 +769,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Return whether there are any clearable notifications */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) boolean hasActiveClearableNotifications(@SelectedRows int selection) { return mController.hasActiveClearableNotifications(selection); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -795,7 +789,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.DECORATOR) protected void onDraw(Canvas canvas) { if (mShouldDrawNotificationBackground && (mSections[0].getCurrentBounds().top @@ -892,7 +885,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return textY; } - @ShadeViewRefactor(RefactorComponent.DECORATOR) private void drawBackground(Canvas canvas) { int lockScreenLeft = mSidePaddings; int lockScreenRight = getWidth() - mSidePaddings; @@ -1020,7 +1012,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void updateBackgroundDimming() { // No need to update the background color if it's not being drawn. if (!mShouldDrawNotificationBackground) { @@ -1043,7 +1034,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void initView(Context context, NotificationSwipeHelper swipeHelper, NotificationStackSizeCalculator notificationStackSizeCalculator) { mScroller = new OverScroller(getContext()); @@ -1104,12 +1094,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void notifyHeightChangeListener(ExpandableView view) { notifyHeightChangeListener(view, false /* needsAnimation */); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) { if (mOnHeightChangedListener != null) { mOnHeightChangedListener.onHeightChanged(view, needsAnimation); @@ -1151,7 +1139,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Trace.beginSection("NotificationStackScrollLayout#onMeasure"); if (SPEW) { @@ -1185,7 +1172,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onLayout(boolean changed, int l, int t, int r, int b) { // we layout all our children centered on the top float centerX = getWidth() / 2.0f; @@ -1216,7 +1202,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAnimateStackYForContentHeightChange = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void requestAnimationOnViewResize(ExpandableNotificationRow row) { if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) { mNeedViewResizeAnimation = true; @@ -1224,19 +1209,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setChildLocationsChangedListener( NotificationLogger.OnChildLocationsChangedListener listener) { mListener = listener; } - @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) private void setMaxLayoutHeight(int maxLayoutHeight) { mMaxLayoutHeight = maxLayoutHeight; updateAlgorithmHeightAndPadding(); } - @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) private void updateAlgorithmHeightAndPadding() { mAmbientState.setLayoutHeight(getLayoutHeight()); mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight); @@ -1244,7 +1226,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAmbientState.setTopPadding(mTopPadding); } - @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) private void updateAlgorithmLayoutMinHeight() { mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition() ? getLayoutMinHeight() : 0); @@ -1254,7 +1235,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Updates the children views according to the stack scroll algorithm. Call this whenever * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateChildren() { updateScrollStateForAddedChildren(); mAmbientState.setCurrentScrollVelocity(mScroller.isFinished() @@ -1268,7 +1248,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); if (!mNeedsAnimation && !mChildrenUpdateRequested) { @@ -1276,7 +1255,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollStateForAddedChildren() { if (mChildrenToAddAnimated.isEmpty()) { return; @@ -1297,7 +1275,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable clampScrollPosition(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateForcedScroll() { if (mForcedScroll != null && (!mForcedScroll.hasFocus() || !mForcedScroll.isAttachedToWindow())) { @@ -1317,7 +1294,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void requestChildrenUpdate() { if (!mChildrenUpdateRequested) { getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater); @@ -1326,12 +1302,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private boolean isCurrentlyAnimating() { return mStateAnimator.isRunning(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void clampScrollPosition() { int scrollRange = getScrollRange(); if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) { @@ -1342,12 +1316,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public int getTopPadding() { return mTopPadding; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void setTopPadding(int topPadding, boolean animate) { if (mTopPadding != topPadding) { boolean shouldAnimate = animate || mAnimateNextTopPaddingChange; @@ -1469,7 +1441,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * * @param height the expanded height of the panel */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); updateStackPosition(); @@ -1563,7 +1534,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setRequestedClipBounds(Rect clipRect) { mRequestedClipBounds = clipRect; updateClipping(); @@ -1572,12 +1542,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Return the height of the content ignoring the footer. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getIntrinsicContentHeight() { return (int) mIntrinsicContentHeight; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void updateClipping() { boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode && !mHeadsUpAnimatingAway; @@ -1603,7 +1571,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return The translation at the beginning when expanding. * Measured relative to the resting position. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private float getExpandTranslationStart() { return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight(); } @@ -1612,7 +1579,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return the position from where the appear transition starts when expanding. * Measured in absolute height. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private float getAppearStartPosition() { if (isHeadsUpTransition()) { final NotificationSection firstVisibleSection = getFirstVisibleSection(); @@ -1629,7 +1595,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * intrinsic height, which also includes whether the notification is system expanded and * is mainly used when dragging down from a heads up notification. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getTopHeadsUpPinnedHeight() { if (mTopHeadsUpEntry == null) { return 0; @@ -1649,7 +1614,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return the position from where the appear transition ends when expanding. * Measured in absolute height. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private float getAppearEndPosition() { int appearPosition = mAmbientState.getStackTopMargin(); int visibleNotifCount = mController.getVisibleNotificationCount(); @@ -1670,13 +1634,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean isHeadsUpTransition() { return mAmbientState.getTrackedHeadsUpRow() != null; } // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float calculateAppearFractionOld(float height) { float appearEndPosition = getAppearEndPosition(); float appearStartPosition = getAppearStartPosition(); @@ -1718,12 +1680,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getStackTranslation() { return mStackTranslation; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setStackTranslation(float stackTranslation) { if (stackTranslation != mStackTranslation) { mStackTranslation = stackTranslation; @@ -1738,17 +1698,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * * @return either the layout height or the externally defined height, whichever is smaller */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private int getLayoutHeight() { return Math.min(mMaxLayoutHeight, mCurrentStackHeight); } - @ShadeViewRefactor(RefactorComponent.ADAPTER) public void setQsHeader(ViewGroup qsHeader) { mQsHeader = qsHeader; } - @ShadeViewRefactor(RefactorComponent.ADAPTER) public static boolean isPinnedHeadsUp(View v) { if (v instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) v; @@ -1757,7 +1714,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } - @ShadeViewRefactor(RefactorComponent.ADAPTER) private boolean isHeadsUp(View v) { if (v instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) v; @@ -1766,7 +1722,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private ExpandableView getChildAtPosition(float touchX, float touchY) { return getChildAtPosition( touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */); @@ -1781,7 +1736,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param ignoreDecors Whether decors can be returned * @return the child at the given location. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) ExpandableView getChildAtPosition(float touchX, float touchY, boolean requireMinHeight, boolean ignoreDecors) { // find the view under the pointer, accounting for GONE views @@ -1829,12 +1783,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setScrollingEnabled(boolean enable) { mScrollingEnabled = enable; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void lockScrollTo(View v) { if (mForcedScroll == v) { return; @@ -1847,7 +1799,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean scrollTo(View v) { ExpandableView expandableView = (ExpandableView) v; int positionInLinearLayout = getPositionInLinearLayout(v); @@ -1869,7 +1820,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return the scroll necessary to make the bottom edge of {@param v} align with the top of * the IME. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int targetScrollForView(ExpandableView v, int positionInLinearLayout) { return positionInLinearLayout + v.getIntrinsicHeight() + getImeInset() - getHeight() @@ -1890,7 +1840,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public WindowInsets onApplyWindowInsets(WindowInsets insets) { if (!mAnimatedInsets) { mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom; @@ -1920,7 +1869,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return insets; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private final Runnable mReclamp = new Runnable() { @Override public void run() { @@ -1932,23 +1880,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setExpandingEnabled(boolean enable) { mExpandHelper.setEnabled(enable); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean isScrollingEnabled() { return mScrollingEnabled; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) boolean onKeyguard() { return mStatusBarState == StatusBarState.KEYGUARD; } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Resources res = getResources(); @@ -1961,7 +1905,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable reinitView(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void dismissViewAnimated( View child, Consumer<Boolean> endRunnable, int delay, long duration) { if (child instanceof SectionHeaderView) { @@ -1979,7 +1922,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable true /* isClearAll */); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void snapViewIfNeeded(NotificationEntry entry) { ExpandableNotificationRow child = entry.getRow(); boolean animate = mIsExpanded || isPinnedHeadsUp(child); @@ -1990,7 +1932,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.ADAPTER) public ViewGroup getViewParentForNotification(NotificationEntry entry) { return this; } @@ -2002,7 +1943,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return The amount of scrolling to be performed by the scroller, * not handled by the overScroll amount. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private float overScrollUp(int deltaY, int range) { deltaY = Math.max(deltaY, 0); float currentTopAmount = getCurrentOverScrollAmount(true); @@ -2036,7 +1976,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return The amount of scrolling to be performed by the scroller, * not handled by the overScroll amount. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private float overScrollDown(int deltaY) { deltaY = Math.min(deltaY, 0); float currentBottomAmount = getCurrentOverScrollAmount(false); @@ -2061,14 +2000,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return scrollAmount; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -2076,7 +2013,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void initOrResetVelocityTracker() { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -2085,12 +2021,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setFinishScrollingCallback(Runnable runnable) { mFinishScrollingCallback = runnable; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void animateScroll() { if (mScroller.computeScrollOffset()) { int oldY = mOwnScrollY; @@ -2139,7 +2073,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only). * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) { int newScrollY = scrollY + deltaY; final int top = -maxOverScrollY; @@ -2167,7 +2100,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param onTop Should the effect be applied on top of the scroller. * @param animate Should an animation be performed. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) { setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true); } @@ -2181,7 +2113,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param animate Should an animation be performed. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setOverScrollAmount(float amount, boolean onTop, boolean animate) { setOverScrollAmount(amount, onTop, animate, true); } @@ -2194,7 +2125,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param animate Should an animation be performed. * @param cancelAnimators Should running animations be cancelled. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators) { setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop)); @@ -2210,7 +2140,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param isRubberbanded The value which will be passed to * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged} */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators, boolean isRubberbanded) { if (cancelAnimators) { @@ -2219,7 +2148,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, boolean isRubberbanded) { amount = Math.max(0, amount); @@ -2236,7 +2164,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) { mExpandHelper.onlyObserveMovements(amount > 1.0f); if (mDontReportNextOverScroll) { @@ -2248,23 +2175,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setOverscrollTopChangedListener( OnOverscrollTopChangedListener overscrollTopChangedListener) { mOverscrollTopChangedListener = overscrollTopChangedListener; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getCurrentOverScrollAmount(boolean top) { return mAmbientState.getOverScrollAmount(top); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getCurrentOverScrolledPixels(boolean top) { return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOverScrolledPixels(float amount, boolean onTop) { if (onTop) { mOverScrolledTopPixels = amount; @@ -2281,7 +2204,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param clampedY Whether this value was clamped by the calling method, meaning we've reached * the overscroll limit. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void onCustomOverScrolled(int scrollY, boolean clampedY) { // Treat animating scrolls differently; see #computeScroll() for why. if (!mScroller.isFinished()) { @@ -2305,7 +2227,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Springs back from an overscroll by stopping the {@link #mScroller} and animating the * overscroll amount back to zero. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void springBack() { int scrollRange = getScrollRange(); boolean overScrolledTop = mOwnScrollY <= 0; @@ -2329,7 +2250,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getScrollRange() { // In current design, it only use the top HUN to treat all of HUNs // although there are more than one HUNs @@ -2346,7 +2266,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return scrollRange; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getImeInset() { // The NotificationStackScrollLayout does not extend all the way to the bottom of the // display. Therefore, subtract that space from the mBottomInset, in order to only include @@ -2358,7 +2277,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return the first child which has visibility unequal to GONE */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public ExpandableView getFirstChildNotGone() { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -2374,7 +2292,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return The first child which has visibility unequal to GONE which is currently below the * given translationY or equal to it. */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -2406,7 +2323,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return the last child which has visibility unequal to GONE */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public ExpandableView getLastChildNotGone() { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { @@ -2432,7 +2348,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return the number of children which have visibility unequal to GONE */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getNotGoneChildCount() { int childCount = getChildCount(); int count = 0; @@ -2445,7 +2360,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return count; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateContentHeight() { final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings; final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0; @@ -2481,12 +2395,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard()); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean hasPulsingNotifications() { return mPulsing; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateScrollability() { boolean scrollable = !mQsFullScreen && getScrollRange() > 0; if (scrollable != mScrollable) { @@ -2496,7 +2408,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateForwardAndBackwardScrollability() { boolean forwardScrollable = mScrollable && !mScrollAdapter.isScrolledToBottom(); boolean backwardsScrollable = mScrollable && !mScrollAdapter.isScrolledToTop(); @@ -2509,7 +2420,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateBackground() { // No need to update the background color if it's not being drawn. if (!mShouldDrawNotificationBackground) { @@ -2540,7 +2450,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAnimateNextSectionBoundsChange = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void abortBackgroundAnimators() { for (NotificationSection section : mSections) { section.cancelAnimators(); @@ -2556,7 +2465,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private boolean areSectionBoundsAnimating() { for (NotificationSection section : mSections) { if (section.areBoundsAnimating()) { @@ -2566,7 +2474,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void startBackgroundAnimation() { // TODO(kprevas): do we still need separate fields for top/bottom? // or can each section manage its own animation state? @@ -2586,7 +2493,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Update the background bounds to the new desired bounds */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateBackgroundBounds() { int left = mSidePaddings; int right = getWidth() - mSidePaddings; @@ -2652,7 +2558,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return null; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private ExpandableView getLastChildWithBackground() { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { @@ -2665,7 +2570,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return null; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private ExpandableView getFirstChildWithBackground() { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -2700,7 +2604,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * numbers mean that the finger/cursor is moving down the screen, * which means we want to scroll towards the top. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void fling(int velocityY) { if (getChildCount() > 0) { float topAmount = getCurrentOverScrollAmount(true); @@ -2739,7 +2642,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @return Whether a fling performed on the top overscroll edge lead to the expanded * overScroll view (i.e QS). */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean shouldOverScrollFling(int initialVelocity) { float topOverScroll = getCurrentOverScrollAmount(true); return mScrolledToTopOnFirstDown @@ -2756,7 +2658,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param qsHeight the top padding imposed by the quick settings panel * @param animate whether to animate the change */ - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void updateTopPadding(float qsHeight, boolean animate) { int topPadding = (int) qsHeight; int minStackHeight = getLayoutMinHeight(); @@ -2769,12 +2670,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setExpandedHeight(mExpandedHeight); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setMaxTopPadding(int maxTopPadding) { mMaxTopPadding = maxTopPadding; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getLayoutMinHeight() { if (isHeadsUpTransition()) { ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow(); @@ -2791,17 +2690,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getTopPaddingOverflow() { return mTopPaddingOverflow; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int clampPadding(int desiredPadding) { return Math.max(desiredPadding, mIntrinsicPadding); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private float getRubberBandFactor(boolean onTop) { if (!onTop) { return RUBBER_BAND_FACTOR_NORMAL; @@ -2821,14 +2717,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * rubberbanded, false if it is technically an overscroll but rather a motion to expand the * overscroll view (e.g. expand QS). */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean isRubberbanded(boolean onTop) { return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking || !mScrolledToTopOnFirstDown; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setChildTransferInProgress(boolean childTransferInProgress) { Assert.isMainThread(); mChildTransferInProgress = childTransferInProgress; @@ -2843,7 +2737,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mOnNotificationRemovedListener = listener; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @Override public void onViewRemoved(View child) { super.onViewRemoved(child); @@ -2864,7 +2757,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void cleanUpViewStateForEntry(NotificationEntry entry) { View child = entry.getRow(); if (child == mSwipeHelper.getTranslatingParentView()) { @@ -2872,7 +2764,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void onViewRemovedInternal(ExpandableView child, ViewGroup container) { if (mChangePositionInProgress) { // This is only a position change, don't do anything special @@ -2909,7 +2800,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child)); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void focusNextViewIfFocused(View view) { if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; @@ -2929,7 +2819,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } - @ShadeViewRefactor(RefactorComponent.ADAPTER) private boolean isChildInGroup(View child) { return child instanceof ExpandableNotificationRow && mGroupMembershipManager.isChildInGroup( @@ -2942,7 +2831,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param child The view to generate the remove animation for. * @return Whether an animation was generated. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) boolean generateRemoveAnimation(ExpandableView child) { String key = ""; if (mDebugRemoveAnimation) { @@ -2986,7 +2874,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return false; } - @ShadeViewRefactor(RefactorComponent.ADAPTER) private boolean isClickedHeadsUp(View child) { return HeadsUpUtil.isClickedHeadsUpNotification(child); } @@ -2996,7 +2883,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * * @return whether any child was removed from the list to animate and the view was just added */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) { boolean hasAddEvent = false; for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { @@ -3021,7 +2907,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * * @param removedChild the removed child */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollStateForRemovedChild(ExpandableView removedChild) { final int startingPosition = getPositionInLinearLayout(removedChild); final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements; @@ -3050,7 +2935,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mTopPadding - mQsScrollBoundaryPosition; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private int getIntrinsicHeight(View view) { if (view instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) view; @@ -3059,7 +2943,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return view.getHeight(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getPositionInLinearLayout(View requestedView) { ExpandableNotificationRow childInGroup = null; ExpandableNotificationRow requestedRow = null; @@ -3100,7 +2983,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onViewAdded(View child) { super.onViewAdded(child); if (child instanceof ExpandableView) { @@ -3108,7 +2990,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateFirstAndLastBackgroundViews() { NotificationSection firstSection = getFirstVisibleSection(); NotificationSection lastSection = getLastVisibleSection(); @@ -3136,7 +3017,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable invalidate(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void onViewAddedInternal(ExpandableView child) { updateHideSensitiveForChild(child); child.setOnHeightChangedListener(mOnChildHeightChangedListener); @@ -3154,12 +3034,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void updateHideSensitiveForChild(ExpandableView child) { child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive()); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) { onViewRemovedInternal(row, childrenContainer); } @@ -3168,7 +3046,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable onViewAddedInternal(row); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setAnimationsEnabled(boolean animationsEnabled) { mAnimationsEnabled = animationsEnabled; updateNotificationAnimationStates(); @@ -3179,7 +3056,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateNotificationAnimationStates() { boolean running = mAnimationsEnabled || hasPulsingNotifications(); mShelf.setAnimationsEnabled(running); @@ -3191,13 +3067,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void updateAnimationState(View child) { updateAnimationState((mAnimationsEnabled || hasPulsingNotifications()) && (mIsExpanded || isPinnedHeadsUp(child)), child); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setExpandingNotification(ExpandableNotificationRow row) { if (mExpandingNotificationRow != null && row == null) { // Let's unset the clip path being set during launch @@ -3216,7 +3090,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return v.getParent() == this; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void applyLaunchAnimationParams(LaunchAnimationParameters params) { // Modify the clipping for launching notifications mLaunchAnimationParams = params; @@ -3225,7 +3098,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateAnimationState(boolean running, View child) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; @@ -3233,13 +3105,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) boolean isAddOrRemoveAnimationPending() { return mNeedsAnimation && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty()); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) { if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) { // Generate Animations @@ -3256,7 +3126,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void changeViewPosition(ExpandableView child, int newIndex) { Assert.isMainThread(); if (mChangePositionInProgress) { @@ -3290,7 +3159,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void startAnimationToState() { if (mNeedsAnimation) { generateAllAnimationEvents(); @@ -3308,7 +3176,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGoToFullShadeDelay = 0; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateAllAnimationEvents() { generateHeadsUpAnimationEvents(); generateChildRemovalEvents(); @@ -3324,7 +3191,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable generateAnimateEverythingEvent(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateHeadsUpAnimationEvents() { for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) { ExpandableNotificationRow row = eventPair.first; @@ -3388,13 +3254,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mAddedHeadsUpChildren.clear(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) { return viewState.getYTranslation() + viewState.height >= mAmbientState.getMaxHeadsUpTranslation(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateGroupExpansionEvent() { // Generate a group expansion/collapsing event if there is such a group at all if (mExpandedGroupView != null) { @@ -3404,7 +3268,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateViewResizeEvent() { if (mNeedViewResizeAnimation) { boolean hasDisappearAnimation = false; @@ -3425,7 +3288,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mNeedViewResizeAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateChildRemovalEvents() { for (ExpandableView child : mChildrenToRemoveAnimated) { boolean childWasSwipedOut = mSwipedOutViews.contains(child); @@ -3473,7 +3335,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mChildrenToRemoveAnimated.clear(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generatePositionChangeEvents() { for (ExpandableView child : mChildrenChangingPositions) { Integer duration = null; @@ -3498,7 +3359,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateChildAdditionEvents() { for (ExpandableView child : mChildrenToAddAnimated) { if (mFromMoreCardAdditions.contains(child)) { @@ -3514,7 +3374,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFromMoreCardAdditions.clear(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateTopPaddingEvent() { if (mTopPaddingNeedsAnimation) { AnimationEvent event; @@ -3531,7 +3390,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mTopPaddingNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateActivateEvent() { if (mActivateNeedsAnimation) { mAnimationEvents.add( @@ -3540,7 +3398,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mActivateNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateAnimateEverythingEvent() { if (mEverythingNeedsAnimation) { mAnimationEvents.add( @@ -3549,7 +3406,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mEverythingNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateDimmedEvent() { if (mDimmedNeedsAnimation) { mAnimationEvents.add( @@ -3558,7 +3414,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDimmedNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateHideSensitiveEvent() { if (mHideSensitiveNeedsAnimation) { mAnimationEvents.add( @@ -3567,7 +3422,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mHideSensitiveNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void generateGoToFullShadeEvent() { if (mGoToFullShadeNeedsAnimation) { mAnimationEvents.add( @@ -3576,7 +3430,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mGoToFullShadeNeedsAnimation = false; } - @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { return new StackScrollAlgorithm(context, this); } @@ -3584,7 +3437,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return Whether a y coordinate is inside the content. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean isInContentBounds(float y) { return y < getHeight() - getEmptyBottomMargin(); } @@ -3605,7 +3457,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return super.onTouchEvent(ev); } - @ShadeViewRefactor(RefactorComponent.INPUT) void dispatchDownEventToScroller(MotionEvent ev) { MotionEvent downEvent = MotionEvent.obtain(ev); downEvent.setAction(MotionEvent.ACTION_DOWN); @@ -3614,7 +3465,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public boolean onGenericMotionEvent(MotionEvent event) { if (!isScrollingEnabled() || !mIsExpanded @@ -3650,7 +3500,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return super.onGenericMotionEvent(event); } - @ShadeViewRefactor(RefactorComponent.INPUT) boolean onScrollTouch(MotionEvent ev) { if (!isScrollingEnabled()) { return false; @@ -3807,7 +3656,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mFlingAfterUpEvent; } - @ShadeViewRefactor(RefactorComponent.INPUT) protected boolean isInsideQsHeader(MotionEvent ev) { mQsHeader.getBoundsOnScreen(mQsHeaderBound); /** @@ -3825,7 +3673,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mQsHeaderBound.contains((int) ev.getRawX(), (int) ev.getRawY()); } - @ShadeViewRefactor(RefactorComponent.INPUT) private void onOverScrollFling(boolean open, int initialVelocity) { if (mOverscrollTopChangedListener != null) { mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); @@ -3835,7 +3682,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } - @ShadeViewRefactor(RefactorComponent.INPUT) private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; @@ -3853,7 +3699,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.INPUT) private void endDrag() { setIsBeingDragged(false); @@ -3868,7 +3713,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public boolean onInterceptTouchEvent(MotionEvent ev) { if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { return true; @@ -3876,7 +3720,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return super.onInterceptTouchEvent(ev); } - @ShadeViewRefactor(RefactorComponent.INPUT) void handleEmptySpaceClick(MotionEvent ev) { logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY), mStatusBarState, mTouchIsClick); @@ -3919,7 +3762,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable MotionEvent.actionToString(ev.getActionMasked())); } - @ShadeViewRefactor(RefactorComponent.INPUT) void initDownStates(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mExpandedInThisMotion = false; @@ -3933,7 +3775,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); if (disallowIntercept) { @@ -3941,7 +3782,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.INPUT) boolean onInterceptTouchEventScroll(MotionEvent ev) { if (!isScrollingEnabled()) { return false; @@ -4056,14 +3896,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * @return Whether the specified motion event is actually happening over the content. */ - @ShadeViewRefactor(RefactorComponent.INPUT) private boolean isInContentBounds(MotionEvent event) { return isInContentBounds(event.getY()); } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.INPUT) void setIsBeingDragged(boolean isDragged) { mIsBeingDragged = isDragged; if (isDragged) { @@ -4073,22 +3911,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.INPUT) public void requestDisallowLongPress() { cancelLongPress(); } - @ShadeViewRefactor(RefactorComponent.INPUT) public void requestDisallowDismiss() { mDisallowDismissInThisMotion = true; } - @ShadeViewRefactor(RefactorComponent.INPUT) public void cancelLongPress() { mSwipeHelper.cancelLongPress(); } - @ShadeViewRefactor(RefactorComponent.INPUT) public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { mOnEmptySpaceClickListener = listener; } @@ -4097,7 +3931,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @hide */ @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (super.performAccessibilityActionInternal(action, arguments)) { return true; @@ -4132,7 +3965,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (!hasWindowFocus) { @@ -4141,7 +3973,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void clearChildFocus(View child) { super.clearChildFocus(child); if (mForcedScroll == child) { @@ -4153,7 +3984,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mScrollAdapter.isScrolledToBottom(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) int getEmptyBottomMargin() { int contentHeight; if (mShouldUseSplitNotificationShade) { @@ -4168,13 +3998,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return Math.max(mMaxLayoutHeight - contentHeight, 0); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void onExpansionStarted() { mIsExpansionChanging = true; mAmbientState.setExpansionChanging(true); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void onExpansionStopped() { mIsExpansionChanging = false; mAmbientState.setExpansionChanging(false); @@ -4187,7 +4015,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearUserLockedViews() { for (int i = 0; i < getChildCount(); i++) { ExpandableView child = getChildAtIndex(i); @@ -4198,7 +4025,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearTemporaryViews() { // lets make sure nothing is transient anymore clearTemporaryViewsInGroup(this); @@ -4211,7 +4037,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearTemporaryViewsInGroup(ViewGroup viewGroup) { while (viewGroup != null && viewGroup.getTransientViewCount() != 0) { final View transientView = viewGroup.getTransientView(0); @@ -4222,27 +4047,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void onPanelTrackingStarted() { mPanelTracking = true; mAmbientState.setPanelTracking(true); resetExposedMenuView(true /* animate */, true /* force */); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void onPanelTrackingStopped() { mPanelTracking = false; mAmbientState.setPanelTracking(false); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void resetScrollPosition() { mScroller.abortAnimation(); setOwnScrollY(0); } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.COORDINATOR) void setIsExpanded(boolean isExpanded) { boolean changed = isExpanded != mIsExpanded; mIsExpanded = isExpanded; @@ -4267,7 +4088,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void updateChronometers() { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -4275,7 +4095,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) void updateChronometerForChild(View child) { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; @@ -4316,7 +4135,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateChronometerForChild(view); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateScrollPositionOnExpandInBottom(ExpandableView view) { if (view instanceof ExpandableNotificationRow && !onKeyguard()) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; @@ -4345,13 +4163,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setOnHeightChangedListener( ExpandableView.OnHeightChangedListener onHeightChangedListener) { this.mOnHeightChangedListener = onHeightChangedListener; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void onChildAnimationFinished() { setAnimationRunning(false); requestChildrenUpdate(); @@ -4372,7 +4188,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearHeadsUpDisappearRunning() { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); @@ -4388,7 +4203,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void clearTransient() { for (ExpandableView view : mClearTransientViewsWhenFinished) { view.removeFromTransientContainer(); @@ -4396,7 +4210,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mClearTransientViewsWhenFinished.clear(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void runAnimationFinishedRunnables() { for (Runnable runnable : mAnimationFinishedRunnables) { runnable.run(); @@ -4407,7 +4220,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * See {@link AmbientState#setDimmed}. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setDimmed(boolean dimmed, boolean animate) { dimmed &= onKeyguard(); mAmbientState.setDimmed(dimmed); @@ -4422,18 +4234,15 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) boolean isDimmed() { return mAmbientState.isDimmed(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void setDimAmount(float dimAmount) { mDimAmount = dimAmount; updateBackgroundDimming(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void animateDimmed(boolean dimmed) { if (mDimAnimator != null) { mDimAnimator.cancel(); @@ -4450,7 +4259,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mDimAnimator.start(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void updateSensitiveness(boolean animate, boolean hideSensitive) { if (hideSensitive != mAmbientState.isHideSensitive()) { int childCount = getChildCount(); @@ -4468,7 +4276,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void applyCurrentState() { int numChildren = getChildCount(); for (int i = 0; i < numChildren; i++) { @@ -4485,7 +4292,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateViewShadows(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void updateViewShadows() { // we need to work around an issue where the shadow would not cast between siblings when // their z difference is between 0 and 0.1 @@ -4526,7 +4332,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Update colors of "dismiss" and "empty shade" views. */ - @ShadeViewRefactor(RefactorComponent.DECORATOR) void updateDecorViews() { final @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); @@ -4535,7 +4340,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mEmptyShadeView.setTextColor(textColor); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void goToFullShade(long delay) { mGoToFullShadeNeedsAnimation = true; mGoToFullShadeDelay = delay; @@ -4543,23 +4347,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void cancelExpandHelper() { mExpandHelper.cancel(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) void setIntrinsicPadding(int intrinsicPadding) { mIntrinsicPadding = intrinsicPadding; } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) int getIntrinsicPadding() { return mIntrinsicPadding; } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean shouldDelayChildPressedState() { return true; } @@ -4567,7 +4367,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * See {@link AmbientState#setDozing}. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setDozing(boolean dozing, boolean animate) { if (mAmbientState.isDozing() == dozing) { return; @@ -4586,7 +4385,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the * animation curve. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setHideAmount(float linearHideAmount, float interpolatedHideAmount) { mLinearHideAmount = linearHideAmount; mInterpolatedHideAmount = interpolatedHideAmount; @@ -4627,7 +4425,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mController.updateVisibility(!mAmbientState.isFullyHidden() || !onKeyguard()); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void notifyHideAnimationStart(boolean hide) { // We only swap the scaling factor if we're fully hidden or fully awake to avoid // interpolation issues when playing with the power button. @@ -4639,7 +4436,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private int getNotGoneIndex(View child) { int count = getChildCount(); int notGoneIndex = 0; @@ -4663,7 +4459,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mFooterView != null && mFooterView.isHistoryShown(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setFooterView(@NonNull FooterView footerView) { int index = -1; if (mFooterView != null) { @@ -4677,7 +4472,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setEmptyShadeView(EmptyShadeView emptyShadeView) { int index = -1; if (mEmptyShadeView != null) { @@ -4688,7 +4482,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable addView(mEmptyShadeView, index); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled); @@ -4731,7 +4524,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mEmptyShadeView.isVisible(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { if (mFooterView == null || mNotificationStackSizeCalculator == null) { return; @@ -4743,7 +4535,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setClearAllInProgress(boolean clearAllInProgress) { mClearAllInProgress = clearAllInProgress; mAmbientState.setClearAllInProgress(clearAllInProgress); @@ -4754,19 +4545,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mClearAllInProgress; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean isFooterViewNotGone() { return mFooterView != null && mFooterView.getVisibility() != View.GONE && !mFooterView.willBeGone(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean isFooterViewContentVisible() { return mFooterView != null && mFooterView.isContentVisible(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public int getFooterViewHeightWithPadding() { return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements @@ -4780,12 +4568,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mGapHeight + mPaddingBetweenElements; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public int getEmptyShadeViewHeight() { return mEmptyShadeView.getHeight(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getBottomMostNotificationBottom() { final int count = getChildCount(); float max = 0; @@ -4803,7 +4589,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return max + getStackTranslation(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setCentralSurfaces(CentralSurfaces centralSurfaces) { this.mCentralSurfaces = centralSurfaces; } @@ -4812,7 +4597,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mActivityStarter = activityStarter; } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) void requestAnimateEverything() { if (mIsExpanded && mAnimationsEnabled) { mEverythingNeedsAnimation = true; @@ -4821,7 +4605,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public boolean isBelowLastNotification(float touchX, float touchY) { int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { @@ -4856,7 +4639,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @hide */ @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); event.setScrollable(mScrollable); @@ -4866,7 +4648,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); if (mScrollable) { @@ -4885,7 +4666,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable info.setClassName(ScrollView.class.getName()); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void generateChildOrderChangedEvent() { if (mIsExpanded && mAnimationsEnabled) { mGenerateChildOrderChangedEvent = true; @@ -4894,17 +4674,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public int getContainerChildCount() { return getChildCount(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public View getContainerChildAt(int i) { return getChildAt(i); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void removeContainerView(View v) { Assert.isMainThread(); removeView(v); @@ -4916,7 +4693,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateSpeedBumpIndex(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void addContainerView(View v) { Assert.isMainThread(); addView(v); @@ -4950,7 +4726,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void runAfterAnimationFinished(Runnable runnable) { mAnimationFinishedRunnables.add(runnable); } @@ -4960,7 +4735,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable generateHeadsUpAnimation(row, isHeadsUp); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed); if (SPEW) { @@ -4995,7 +4769,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param height the height of the screen * @param bottomBarHeight the height of the bar on the bottom */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setHeadsUpBoundaries(int height, int bottomBarHeight) { mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight); mStateAnimator.setHeadsUpAppearHeightBottom(height); @@ -5006,23 +4779,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mWillExpand = willExpand; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setTrackingHeadsUp(ExpandableNotificationRow row) { mAmbientState.setTrackedHeadsUpRow(row); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void forceNoOverlappingRendering(boolean force) { mForceNoOverlappingRendering = force; } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean hasOverlappingRendering() { return !mForceNoOverlappingRendering && super.hasOverlappingRendering(); } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void setAnimationRunning(boolean animationRunning) { if (animationRunning != mAnimationRunning) { if (animationRunning) { @@ -5035,12 +4804,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean isExpanded() { return mIsExpanded; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setPulsing(boolean pulsing, boolean animated) { if (!mPulsing && !pulsing) { return; @@ -5055,7 +4822,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable notifyHeightChangeListener(null, animated); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsFullScreen(boolean qsFullScreen) { mQsFullScreen = qsFullScreen; updateAlgorithmLayoutMinHeight(); @@ -5066,7 +4832,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mQsFullScreen; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { boolean footerAffected = mQsExpansionFraction != qsExpansionFraction && (mQsExpansionFraction == 1 || qsExpansionFraction == 1); @@ -5084,12 +4849,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.COORDINATOR) void setOwnScrollY(int ownScrollY) { setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) { // Avoid Flicking during clear all // when the shade finishes closing, onExpansionStopped will call @@ -5142,7 +4905,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager()); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setShelfController(NotificationShelfController notificationShelfController) { NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); int index = -1; @@ -5157,7 +4919,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable notificationShelfController.bind(mAmbientState, mController); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setMaxDisplayedNotifications(int maxDisplayedNotifications) { if (mMaxDisplayedNotifications != maxDisplayedNotifications) { mMaxDisplayedNotifications = maxDisplayedNotifications; @@ -5176,13 +4937,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mKeyguardBottomPadding = keyguardBottomPadding; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) { mShouldShowShelfOnly = shouldShowShelfOnly; updateAlgorithmLayoutMinHeight(); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public int getMinExpansionHeight() { // shelf height is defined in dp but status bar height can be defined in px, that makes // relation between them variable - sometimes one might be bigger than the other when @@ -5193,19 +4952,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable + mWaterfallTopInset; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) { mInHeadsUpPinnedMode = inHeadsUpPinnedMode; updateClipping(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) { mHeadsUpAnimatingAway = headsUpAnimatingAway; updateClipping(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @VisibleForTesting public void setStatusBarState(int statusBarState) { mStatusBarState = statusBarState; @@ -5238,12 +4994,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateVisibility(); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setExpandingVelocity(float expandingVelocity) { mAmbientState.setExpandingVelocity(expandingVelocity); } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public float getOpeningHeight() { if (mEmptyShadeView.getVisibility() == GONE) { return getMinExpansionHeight(); @@ -5252,12 +5006,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setIsFullWidth(boolean isFullWidth) { mAmbientState.setSmallScreen(isFullWidth); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setUnlockHintRunning(boolean running) { mAmbientState.setUnlockHintRunning(running); if (!running) { @@ -5266,7 +5018,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setPanelFlinging(boolean flinging) { mAmbientState.setFlinging(flinging); if (!flinging) { @@ -5275,12 +5026,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) { mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void dump(PrintWriter pwOriginal, String[] args) { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); pw.println("Internal state:"); @@ -5376,7 +5125,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable }); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public boolean isFullyHidden() { return mAmbientState.isFullyHidden(); } @@ -5387,7 +5135,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * * @param listener the listener to notify. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { mExpandedHeightListeners.add(listener); } @@ -5395,12 +5142,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Stop a listener from listening to the expandedHeight. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) { mExpandedHeightListeners.remove(listener); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) void setHeadsUpAppearanceController( HeadsUpAppearanceController headsUpAppearanceController) { mHeadsUpAppearanceController = headsUpAppearanceController; @@ -5492,7 +5237,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @VisibleForTesting void clearNotifications(@SelectedRows int selection, boolean closeShade) { // Animate-swipe all dismissable notifications, then animate the shade closed @@ -5553,7 +5297,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } @VisibleForTesting - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void inflateFooterView() { FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( R.layout.status_bar_notification_footer, this, false); @@ -5567,7 +5310,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setFooterView(footerView); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void inflateEmptyShadeView() { EmptyShadeView oldView = mEmptyShadeView; EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate( @@ -5589,7 +5331,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * Updates expanded, dimmed and locked states of notification rows. */ - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void onUpdateRowStates() { // The following views will be moved to the end of mStackScroller. This counter represents @@ -6061,7 +5802,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * A listener that is notified when the empty space below the notifications is clicked on */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public interface OnEmptySpaceClickListener { void onEmptySpaceClicked(float x, float y); } @@ -6069,7 +5809,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable /** * A listener that gets notified when the overscroll at the top has changed. */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public interface OnOverscrollTopChangedListener { /** @@ -6093,7 +5832,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void flingTopOverscroll(float velocity, boolean open); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void updateSpeedBumpIndex() { mSpeedBumpIndexDirty = true; } @@ -6129,7 +5867,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private void resetExposedMenuView(boolean animate, boolean force) { mSwipeHelper.resetExposedMenuView(animate, force); } @@ -6149,7 +5886,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) static class AnimationEvent { static AnimationFilter[] FILTERS = new AnimationFilter[]{ @@ -6438,7 +6174,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setCheckForLeaveBehind(true); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() { @Override public ExpandableView getChildAtRawPosition(float touchX, float touchY) { @@ -6477,7 +6212,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable }); } - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() { @Override public ExpandableView getChildAtPosition(float touchX, float touchY) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt index d9dc8878fa61..bbb4f2449330 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt @@ -236,7 +236,11 @@ constructor( override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) { postOnUiThread(delay) { - activityStarterInternal.startActivityDismissingKeyguard(intent = intent) + activityStarterInternal.startActivityDismissingKeyguard( + intent = intent, + onlyProvisioned = true, + dismissShade = true, + ) } } @@ -248,6 +252,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, ) } @@ -262,6 +268,8 @@ constructor( postOnUiThread(delay) { activityStarterInternal.startActivityDismissingKeyguard( intent = intent, + onlyProvisioned = true, + dismissShade = true, animationController = animationController, customMessage = customMessage, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 618120d406cb..7312db6595e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -20,6 +20,7 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING; import android.annotation.NonNull; +import android.graphics.Point; import android.os.Bundle; import android.os.PowerManager; import android.os.SystemClock; @@ -38,6 +39,7 @@ import com.android.systemui.doze.DozeLog; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.BurnInInteractor; +import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -99,6 +101,7 @@ public final class DozeServiceHost implements DozeHost { private CentralSurfaces mCentralSurfaces; private boolean mAlwaysOnSuppressed; private boolean mPulsePending; + private DozeInteractor mDozeInteractor; @Inject public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager, @@ -115,6 +118,7 @@ public final class DozeServiceHost implements DozeHost { NotificationWakeUpCoordinator notificationWakeUpCoordinator, AuthController authController, NotificationIconAreaController notificationIconAreaController, + DozeInteractor dozeInteractor, BurnInInteractor burnInInteractor) { super(); mDozeLog = dozeLog; @@ -136,6 +140,7 @@ public final class DozeServiceHost implements DozeHost { mNotificationIconAreaController = notificationIconAreaController; mBurnInInteractor = burnInInteractor; mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener); + mDozeInteractor = dozeInteractor; } // TODO: we should try to not pass status bar in here if we can avoid it. @@ -226,6 +231,7 @@ public final class DozeServiceHost implements DozeHost { for (Callback callback : mCallbacks) { callback.onDozingChanged(dozing); } + mDozeInteractor.setIsDozing(dozing); mStatusBarStateController.setIsDozing(dozing); } @@ -360,7 +366,14 @@ public final class DozeServiceHost implements DozeHost { @Override public void onSlpiTap(float screenX, float screenY) { - if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null + if (screenX < 0 || screenY < 0) return; + dispatchTouchEventToAmbientIndicationContainer(screenX, screenY); + + mDozeInteractor.setLastTapToWakePosition(new Point((int) screenX, (int) screenY)); + } + + private void dispatchTouchEventToAmbientIndicationContainer(float screenX, float screenY) { + if (mAmbientIndicationContainer != null && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) { int[] locationOnScreen = new int[2]; mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index a058bf88e2e4..a6b2bd89bfa5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -103,6 +103,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private boolean mQsCustomizing; private boolean mQsExpanded; + private boolean mBouncerVisible; private boolean mGlobalActionsVisible; private boolean mDirectReplying; @@ -188,9 +189,10 @@ public class LightBarController implements BatteryController.BatteryStateChangeC final boolean ignoreScrimForce = mDirectReplying && mNavbarColorManagedByIme; final boolean darkForScrim = mForceDarkForScrim && !ignoreScrimForce; final boolean lightForScrim = mForceLightForScrim && !ignoreScrimForce; - final boolean darkForQs = mQsCustomizing || mQsExpanded || mGlobalActionsVisible; + final boolean darkForQs = (mQsCustomizing || mQsExpanded) && !mBouncerVisible; + final boolean darkForTop = darkForQs || mGlobalActionsVisible; mNavigationLight = - ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForQs; + ((mHasLightNavigationBar && !darkForScrim) || lightForScrim) && !darkForTop; mLastNavigationBarAppearanceChangedLog = "onNavigationBarAppearanceChanged()" + " appearance=" + appearance + " nbModeChanged=" + nbModeChanged @@ -201,6 +203,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC + " darkForScrim=" + darkForScrim + " lightForScrim=" + lightForScrim + " darkForQs=" + darkForQs + + " darkForTop=" + darkForTop + " mNavigationLight=" + mNavigationLight + " last=" + last + " timestamp=" + new Date(); @@ -298,15 +301,20 @@ public class LightBarController implements BatteryController.BatteryStateChangeC public void setScrimState(ScrimState scrimState, float scrimBehindAlpha, GradientColors scrimInFrontColor) { if (mUseNewLightBarLogic) { + boolean bouncerVisibleLast = mBouncerVisible; boolean forceDarkForScrimLast = mForceDarkForScrim; boolean forceLightForScrimLast = mForceLightForScrim; - final boolean forceForScrim = - scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD; + mBouncerVisible = + scrimState == ScrimState.BOUNCER || scrimState == ScrimState.BOUNCER_SCRIMMED; + final boolean forceForScrim = mBouncerVisible + || scrimBehindAlpha >= NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD; final boolean scrimColorIsLight = scrimInFrontColor.supportsDarkText(); mForceDarkForScrim = forceForScrim && !scrimColorIsLight; mForceLightForScrim = forceForScrim && scrimColorIsLight; - if (mHasLightNavigationBar) { + if (mBouncerVisible != bouncerVisibleLast) { + reevaluate(); + } else if (mHasLightNavigationBar) { if (mForceDarkForScrim != forceDarkForScrimLast) reevaluate(); } else { if (mForceLightForScrim != forceLightForScrimLast) reevaluate(); @@ -318,6 +326,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC + " forceForScrim=" + forceForScrim + " scrimColorIsLight=" + scrimColorIsLight + " mHasLightNavigationBar=" + mHasLightNavigationBar + + " mBouncerVisible=" + mBouncerVisible + " mForceDarkForScrim=" + mForceDarkForScrim + " mForceLightForScrim=" + mForceLightForScrim + " timestamp=" + new Date(); @@ -428,6 +437,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC pw.println(); pw.print(" mQsCustomizing="); pw.println(mQsCustomizing); pw.print(" mQsExpanded="); pw.println(mQsExpanded); + pw.print(" mBouncerVisible="); pw.println(mBouncerVisible); pw.print(" mGlobalActionsVisible="); pw.println(mGlobalActionsVisible); pw.print(" mDirectReplying="); pw.println(mDirectReplying); pw.print(" mNavbarColorManagedByIme="); pw.println(mNavbarColorManagedByIme); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 4ae2edcd007a..4ccbc5a12ea0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; @@ -158,6 +159,7 @@ public abstract class StatusBarViewModule { StatusBarIconController statusBarIconController, StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, CollapsedStatusBarViewModel collapsedStatusBarViewModel, + CollapsedStatusBarViewBinder collapsedStatusBarViewBinder, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, @@ -182,6 +184,7 @@ public abstract class StatusBarViewModule { statusBarIconController, darkIconManagerFactory, collapsedStatusBarViewModel, + collapsedStatusBarViewBinder, statusBarHideIconsForBouncerManager, keyguardStateController, shadeViewController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 0651a7b2363f..fcae23b068bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -14,8 +14,6 @@ package com.android.systemui.statusbar.phone.fragment; - - import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; @@ -69,6 +67,7 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder; +import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; @@ -131,6 +130,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final StatusBarIconController mStatusBarIconController; private final CarrierConfigTracker mCarrierConfigTracker; private final CollapsedStatusBarViewModel mCollapsedStatusBarViewModel; + private final CollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory; private final SecureSettings mSecureSettings; @@ -183,11 +183,21 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false; /** + * True when a transition from lockscreen to dream has started, but haven't yet received a + * status bar window state change afterward. + * + * Similar to [mWaitingForWindowStateChangeAfterCameraLaunch]. + */ + private boolean mTransitionFromLockscreenToDreamStarted = false; + + /** * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives * a new status bar window state. */ - private final StatusBarWindowStateListener mStatusBarWindowStateListener = state -> - mWaitingForWindowStateChangeAfterCameraLaunch = false; + private final StatusBarWindowStateListener mStatusBarWindowStateListener = state -> { + mWaitingForWindowStateChangeAfterCameraLaunch = false; + mTransitionFromLockscreenToDreamStarted = false; + }; @SuppressLint("ValidFragment") public CollapsedStatusBarFragment( @@ -201,6 +211,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue StatusBarIconController statusBarIconController, StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory, CollapsedStatusBarViewModel collapsedStatusBarViewModel, + CollapsedStatusBarViewBinder collapsedStatusBarViewBinder, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, KeyguardStateController keyguardStateController, ShadeViewController shadeViewController, @@ -224,6 +235,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mFeatureFlags = featureFlags; mStatusBarIconController = statusBarIconController; mCollapsedStatusBarViewModel = collapsedStatusBarViewModel; + mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mDarkIconManagerFactory = darkIconManagerFactory; mKeyguardStateController = keyguardStateController; @@ -296,8 +308,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mCarrierConfigTracker.addCallback(mCarrierConfigCallback); mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); - CollapsedStatusBarViewBinder.bind( - mStatusBar, mCollapsedStatusBarViewModel, this::updateStatusBarVisibilities); + mCollapsedStatusBarViewBinder.bind( + mStatusBar, mCollapsedStatusBarViewModel, mStatusBarVisibilityChangeListener); } @Override @@ -411,6 +423,19 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return mStatusBarFragmentComponent; } + private StatusBarVisibilityChangeListener mStatusBarVisibilityChangeListener = + new StatusBarVisibilityChangeListener() { + @Override + public void onStatusBarVisibilityMaybeChanged() { + updateStatusBarVisibilities(/* animate= */ true); + } + + @Override + public void onTransitionFromLockscreenToDreamStarted() { + mTransitionFromLockscreenToDreamStarted = true; + } + }; + @Override public void disable(int displayId, int state1, int state2, boolean animate) { if (displayId != getContext().getDisplayId()) { @@ -423,10 +448,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue updateStatusBarVisibilities(animate); } - private void updateStatusBarVisibilities() { - updateStatusBarVisibilities(/* animate= */ true); - } - private void updateStatusBarVisibilities(boolean animate) { StatusBarVisibilityModel previousModel = mLastModifiedVisibility; StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility); @@ -546,6 +567,18 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return true; } + // Similar to [hideIconsForSecureCamera]: When dream is launched over lockscreen, the icons + // are momentarily visible because the dream animation has finished, but SysUI has not been + // informed that the dream is full-screen. For extra safety, we double-check that we're + // still dreaming. + final boolean hideIconsForDream = + mTransitionFromLockscreenToDreamStarted + && mKeyguardUpdateMonitor.isDreaming() + && mKeyguardStateController.isOccluded(); + if (hideIconsForDream) { + return true; + } + // While the status bar is transitioning from lockscreen to an occluded, we don't yet know // if the occluding activity is fullscreen or not. If it *is* fullscreen, we don't want to // briefly show the status bar just to immediately hide it again. So, we wait for the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index 49de5a232f30..27cc64f9a8e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -44,6 +44,8 @@ import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerPr import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl +import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder +import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher @@ -107,6 +109,11 @@ abstract class StatusBarPipelineModule { impl: CollapsedStatusBarViewModelImpl ): CollapsedStatusBarViewModel + @Binds + abstract fun collapsedStatusBarViewBinder( + impl: CollapsedStatusBarViewBinderImpl + ): CollapsedStatusBarViewBinder + companion object { @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 81a068d10a14..a47f95d69c65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -233,6 +233,7 @@ constructor( override val defaultDataSubRatConfig: StateFlow<Config> = merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) + .onStart { emit(Unit) } .mapLatest { Config.readConfig(context) } .distinctUntilChanged() .onEach { logger.logDefaultDataSubRatConfig(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 9a59851a230a..b9b88f4b762c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -19,34 +19,61 @@ package com.android.systemui.statusbar.pipeline.shared.ui.binder import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel +import javax.inject.Inject +import kotlinx.coroutines.launch -object CollapsedStatusBarViewBinder { +/** + * Interface to assist with binding the [CollapsedStatusBarFragment] to + * [CollapsedStatusBarViewModel]. Used only to enable easy testing of [CollapsedStatusBarFragment]. + */ +interface CollapsedStatusBarViewBinder { /** * Binds the view to the view-model. [listener] will be notified whenever an event that may * change the status bar visibility occurs. */ - @JvmStatic fun bind( view: View, viewModel: CollapsedStatusBarViewModel, listener: StatusBarVisibilityChangeListener, + ) +} + +@SysUISingleton +class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBarViewBinder { + override fun bind( + view: View, + viewModel: CollapsedStatusBarViewModel, + listener: StatusBarVisibilityChangeListener, ) { view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { - viewModel.isTransitioningFromLockscreenToOccluded.collect { - listener.onStatusBarVisibilityMaybeChanged() + launch { + viewModel.isTransitioningFromLockscreenToOccluded.collect { + listener.onStatusBarVisibilityMaybeChanged() + } + } + + launch { + viewModel.transitionFromLockscreenToDreamStartedEvent.collect { + listener.onTransitionFromLockscreenToDreamStarted() + } } } } } } -/** - * Listener to be notified when the status bar visibility might have changed due to the device - * moving to a different state. - */ -fun interface StatusBarVisibilityChangeListener { +/** Listener for various events that may affect the status bar's visibility. */ +interface StatusBarVisibilityChangeListener { + /** + * Called when the status bar visibility might have changed due to the device moving to a + * different state. + */ fun onStatusBarVisibilityMaybeChanged() + + /** Called when a transition from lockscreen to dream has started. */ + fun onTransitionFromLockscreenToDreamStarted() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index edb7e4daca1b..15ab143a7aeb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -22,8 +22,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionState import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -43,6 +45,9 @@ interface CollapsedStatusBarViewModel { * otherwise. */ val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> + + /** Emits whenever a transition from lockscreen to dream has started. */ + val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> } @SysUISingleton @@ -59,4 +64,9 @@ constructor( it.transitionState == TransitionState.RUNNING } .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) + + override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> = + keyguardTransitionInteractor.lockscreenToDreamingTransition + .filter { it.transitionState == TransitionState.STARTED } + .map {} } diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt index 3667392b515e..c1b86ab77ab8 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt @@ -50,14 +50,6 @@ constructor( } } - override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) { - stylusUsiPowerUi.refresh() - } - - override fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) { - stylusUsiPowerUi.refresh() - } - override fun onStylusUsiBatteryStateChanged( deviceId: Int, eventTimeMillis: Long, diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt index 6eddd9eb7ad2..3e1c13c1cba8 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt @@ -94,12 +94,17 @@ constructor( return@refreshNotification } + // Only hide notification in two cases: battery has been recharged above the + // threshold, or user has dismissed or clicked notification ("suppression"). + if (suppressed || !batteryBelowThreshold) { + hideNotification() + } + if (!batteryBelowThreshold) { // Reset suppression when stylus battery is recharged, so that the next time // it reaches a low battery, the notification will show again. suppressed = false } - hideNotification() } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt index 58f22466d44d..57b9f914d4d6 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt +++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt @@ -56,18 +56,6 @@ class DynamicColors { Pair.create("on_error", MDC.onError()), Pair.create("error_container", MDC.errorContainer()), Pair.create("on_error_container", MDC.onErrorContainer()), - Pair.create("primary_fixed", MDC.primaryFixed()), - Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), - Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), - Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), - Pair.create("secondary_fixed", MDC.secondaryFixed()), - Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), - Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), - Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), - Pair.create("tertiary_fixed", MDC.tertiaryFixed()), - Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), - Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), - Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), Pair.create("control_activated", MDC.controlActivated()), Pair.create("control_normal", MDC.controlNormal()), Pair.create("control_highlight", MDC.controlHighlight()), @@ -92,7 +80,24 @@ class DynamicColors { Pair.create( "palette_key_color_neutral_variant", MDC.neutralVariantPaletteKeyColor() - ) + ), + ) + + @JvmField + val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> = + arrayListOf( + Pair.create("primary_fixed", MDC.primaryFixed()), + Pair.create("primary_fixed_dim", MDC.primaryFixedDim()), + Pair.create("on_primary_fixed", MDC.onPrimaryFixed()), + Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()), + Pair.create("secondary_fixed", MDC.secondaryFixed()), + Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()), + Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()), + Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()), + Pair.create("tertiary_fixed", MDC.tertiaryFixed()), + Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()), + Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()), + Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 4b73d6190d99..c1999b284553 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -624,6 +624,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { FabricatedOverlay overlay = newFabricatedOverlay("dynamic"); assignDynamicPaletteToOverlay(overlay, true /* isDark */); assignDynamicPaletteToOverlay(overlay, false /* isDark */); + assignFixedColorsToOverlay(overlay); return overlay; } @@ -638,6 +639,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { }); } + private void assignFixedColorsToOverlay(FabricatedOverlay overlay) { + DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> { + String resourceName = "android:color/system_" + p.first; + int colorValue = p.second.getArgb(mDynamicSchemeLight); + overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue, + null /* configuration */); + }); + } + /** * Checks if the color scheme in mColorScheme matches the current system palettes. * @param managedProfiles List of managed profiles for this user. @@ -666,7 +676,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { && res.getColor(android.R.color.system_primary_container_dark, theme) == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark) && res.getColor(android.R.color.system_primary_container_light, theme) - == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight))) { + == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight) + && res.getColor(android.R.color.system_primary_fixed, theme) + == MaterialDynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) { return false; } } diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 0a78d896e1bd..d9a8e0cfb53a 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -23,8 +23,6 @@ import android.content.Context; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import androidx.annotation.Nullable; - import com.android.internal.logging.UiEventLogger; import com.android.keyguard.KeyguardViewController; import com.android.systemui.dagger.ReferenceSystemUIModule; @@ -75,13 +73,13 @@ import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl; import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler; import com.android.systemui.volume.dagger.VolumeModule; -import javax.inject.Named; - import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoSet; +import javax.inject.Named; + /** * A TV specific version of {@link ReferenceSystemUIModule}. * @@ -105,9 +103,8 @@ public abstract class TvSystemUIModule { @SysUISingleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) - @Nullable static String provideLeakReportEmail() { - return null; + return ""; } @Binds diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt new file mode 100644 index 000000000000..73e2f97d92ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt @@ -0,0 +1,40 @@ +/* + * 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.util.kotlin + +class Utils { + companion object { + fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second) + + fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d) + fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = + Quad(a, bcd.first, bcd.second, bcd.third) + + fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) = + Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth) + } +} + +data class Quad<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D) + +data class Quint<A, B, C, D, E>( + val first: A, + val second: B, + val third: C, + val fourth: D, + val fifth: E +) diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java index a0d22f388cbc..c1b7d7a874f3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java @@ -18,7 +18,6 @@ package com.android.systemui.util.leak; import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; -import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -29,6 +28,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Debug; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Log; import androidx.core.content.FileProvider; @@ -68,7 +68,7 @@ public class LeakReporter { @Inject public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector, - @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) { + @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) { mContext = context; mUserTracker = userTracker; mLeakDetector = leakDetector; @@ -150,9 +150,8 @@ public class LeakReporter { intent.setClipData(clipData); intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); - String leakReportEmail = mLeakReportEmail; - if (leakReportEmail != null) { - intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail }); + if (!TextUtils.isEmpty(mLeakReportEmail)) { + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mLeakReportEmail }); } return intent; diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index fc0033d71844..2d1e622fbdce 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -98,6 +98,8 @@ public class Events { public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8; public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9; public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10; + public static final int DISMISS_REASON_POSTURE_CHANGED = 11; + public static final String[] DISMISS_REASONS = { "unknown", "touch_outside", @@ -109,7 +111,8 @@ public class Events { "a11y_stream_changed", "output_chooser", "usb_temperature_below_threshold", - "csd_warning_timeout" + "csd_warning_timeout", + "posture_changed" }; public static final int SHOW_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 91078dc65477..f893cf4694f9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; +import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; import android.animation.Animator; @@ -129,6 +130,7 @@ import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.AlphaTintDrawableWrapper; import com.android.systemui.util.DeviceConfigProxy; @@ -184,7 +186,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final boolean mChangeVolumeRowTintWhenInactive; private final Context mContext; - private final H mHandler = new H(); + private final H mHandler; private final VolumeDialogController mController; private final DeviceProvisionedController mDeviceProvisionedController; private final Region mTouchableRegion = new Region(); @@ -259,16 +261,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); - private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; - private boolean mShowing; private boolean mShowA11yStream; - private int mActiveStream; private int mPrevActiveStream; private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; @@ -300,6 +299,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @VisibleForTesting int mVolumeRingerMuteIconDrawableId; + private int mOriginalGravity; + private final DevicePostureController.Callback mDevicePostureControllerCallback; + private final DevicePostureController mDevicePostureController; + private @DevicePostureController.DevicePostureInt int mDevicePosture; + private int mOrientation; + public VolumeDialogImpl( Context context, VolumeDialogController volumeDialogController, @@ -313,9 +318,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, DeviceConfigProxy deviceConfigProxy, Executor executor, CsdWarningDialog.Factory csdWarningDialogFactory, + DevicePostureController devicePostureController, + Looper looper, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); + mHandler = new H(looper); mController = volumeDialogController; mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -357,6 +365,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, initDimens(); + mOrientation = mContext.getResources().getConfiguration().orientation; + mDevicePostureController = devicePostureController; + if (mDevicePostureController != null) { + int initialPosture = mDevicePostureController.getDevicePosture(); + mDevicePosture = initialPosture; + mDevicePostureControllerCallback = this::onPostureChanged; + } else { + mDevicePostureControllerCallback = null; + } + mDeviceConfigProxy = deviceConfigProxy; mExecutor = executor; mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -365,6 +383,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } /** + * Adjust the dialog location on the screen in order to avoid drawing on the hinge. + */ + private void adjustPositionOnScreen() { + final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT; + final boolean isHalfOpen = + mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED; + final boolean isTabletop = isPortrait && isHalfOpen; + WindowManager.LayoutParams lp = mWindow.getAttributes(); + int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity; + mWindowGravity = Gravity.getAbsoluteGravity(gravity, + mContext.getResources().getConfiguration().getLayoutDirection()); + lp.gravity = mWindowGravity; + } + + @VisibleForTesting int getWindowGravity() { + return mWindowGravity; + } + + /** * If ringer and notification are the same stream (T and earlier), use notification-like bell * icon set. * If ringer and notification are separated, then use generic speaker icons. @@ -419,6 +456,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mExecutor, this::onDeviceConfigChange); + + if (mDevicePostureController != null) { + mDevicePostureController.addCallback(mDevicePostureControllerCallback); + } } @Override @@ -427,6 +468,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeCallbacksAndMessages(null); mConfigurationController.removeCallback(this); mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange); + if (mDevicePostureController != null) { + mDevicePostureController.removeCallback(mDevicePostureControllerCallback); + } } /** @@ -441,7 +485,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mSeparateNotification = newVal; updateRingerModeIconSet(); updateRingRowIcon(); - } } } @@ -500,7 +543,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private void initDialog(int lockTaskModeState) { mDialog = new CustomDialog(mContext); - initDimens(); mConfigurableTexts = new ConfigurableTexts(mContext); @@ -524,14 +566,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, lp.setTitle(VolumeDialogImpl.class.getSimpleName()); lp.windowAnimations = -1; - mWindowGravity = Gravity.getAbsoluteGravity( - mContext.getResources().getInteger(R.integer.volume_dialog_gravity), + mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity); + mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity, mContext.getResources().getConfiguration().getLayoutDirection()); lp.gravity = mWindowGravity; mWindow.setAttributes(lp); mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT); - mDialog.setContentView(R.layout.volume_dialog); mDialogView = mDialog.findViewById(R.id.volume_dialog); mDialogView.setAlpha(0); @@ -1539,8 +1580,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, animator.translationX( (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f); } + animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS, mDialogHideAnimationDurationMs)).start(); + checkODICaptionsTooltip(true); synchronized (mSafetyWarningLock) { if (mSafetyWarning != null) { @@ -2237,6 +2280,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mTopContainer.setBackground(background); } + @Override + public void onConfigChanged(Configuration config) { + mOrientation = config.orientation; + } + private final VolumeDialogController.Callbacks mControllerCallbackH = new VolumeDialogController.Callbacks() { @Override @@ -2313,6 +2361,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } }; + @VisibleForTesting void onPostureChanged(int posture) { + dismiss(DISMISS_REASON_POSTURE_CHANGED); + mDevicePosture = posture; + } + private final class H extends Handler { private static final int SHOW = 1; private static final int DISMISS = 2; @@ -2323,8 +2376,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private static final int STATE_CHANGED = 7; private static final int CSD_TIMEOUT = 8; - public H() { - super(Looper.getMainLooper()); + H(Looper looper) { + super(looper); } @Override @@ -2370,6 +2423,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, protected void onStart() { super.setCanceledOnTouchOutside(true); super.onStart(); + adjustPositionOnScreen(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 14d3ca334073..bb04f82fcffa 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -18,6 +18,7 @@ package com.android.systemui.volume.dagger; import android.content.Context; import android.media.AudioManager; +import android.os.Looper; import com.android.internal.jank.InteractionJankMonitor; import com.android.systemui.dagger.qualifiers.Main; @@ -28,6 +29,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.volume.CsdWarningDialog; @@ -42,7 +44,6 @@ import dagger.Provides; import java.util.concurrent.Executor; - /** Dagger Module for code in the volume package. */ @Module public interface VolumeModule { @@ -65,6 +66,7 @@ public interface VolumeModule { DeviceConfigProxy deviceConfigProxy, @Main Executor executor, CsdWarningDialog.Factory csdFactory, + DevicePostureController devicePostureController, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -79,6 +81,8 @@ public interface VolumeModule { deviceConfigProxy, executor, csdFactory, + devicePostureController, + Looper.getMainLooper(), dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index 492f2318fec6..81d04d4458c0 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -338,7 +338,12 @@ public class WalletScreenController implements */ QAWalletCardViewInfo(Context context, WalletCard walletCard) { mWalletCard = walletCard; - mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + Icon cardImageIcon = mWalletCard.getCardImage(); + if (cardImageIcon.getType() == Icon.TYPE_URI) { + mCardDrawable = null; + } else { + mCardDrawable = mWalletCard.getCardImage().loadDrawable(context); + } Icon icon = mWalletCard.getCardIcon(); mIconDrawable = icon == null ? null : icon.loadDrawable(context); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 19d5278932c2..3f1560bc5fce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -136,7 +136,7 @@ class ClockEventControllerTest : SysuiTestCase() { runBlocking(IMMEDIATE) { underTest.registerListeners(parentView) - repository.setDozing(true) + repository.setIsDozing(true) repository.setDozeAmount(1f) } } 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 9e600f549e0f..7531cb4a91f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -75,7 +75,6 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : MockitoAnnotations.initMocks(this) keyguardBouncerRepository = KeyguardBouncerRepositoryImpl( - mock(com.android.keyguard.ViewMediatorCallback::class.java), FakeSystemClock(), testScope.backgroundScope, bouncerLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt index f3a100bd55e5..239e317b92f5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt @@ -102,6 +102,12 @@ class FingerprintRepositoryImplTest : SysuiTestCase() { 540 /* sensorLocationX */, 1636 /* sensorLocationY */, 130 /* sensorRadius */ + ), + SensorLocationInternal( + "display_id_1" /* displayId */, + 100 /* sensorLocationX */, + 300 /* sensorLocationY */, + 20 /* sensorRadius */ ) ) ) @@ -112,7 +118,17 @@ class FingerprintRepositoryImplTest : SysuiTestCase() { assertThat(repository.sensorId.value).isEqualTo(1) assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG) assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR) - with(repository.sensorLocation.value) { + + assertThat(repository.sensorLocations.value.size).isEqualTo(2) + assertThat(repository.sensorLocations.value).containsKey("display_id_1") + with(repository.sensorLocations.value["display_id_1"]!!) { + assertThat(displayId).isEqualTo("display_id_1") + assertThat(sensorLocationX).isEqualTo(100) + assertThat(sensorLocationY).isEqualTo(300) + assertThat(sensorRadius).isEqualTo(20) + } + assertThat(repository.sensorLocations.value).containsKey("") + with(repository.sensorLocations.value[""]!!) { assertThat(displayId).isEqualTo("") assertThat(sensorLocationX).isEqualTo(540) assertThat(sensorLocationY).isEqualTo(1636) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt new file mode 100644 index 000000000000..fd96cf45504b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt @@ -0,0 +1,91 @@ +/* + * 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.biometrics.domain.interactor + +import android.hardware.biometrics.SensorLocationInternal +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.shared.model.FingerprintSensorType +import com.android.systemui.biometrics.shared.model.SensorStrength +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(JUnit4::class) +class SideFpsOverlayInteractorTest : SysuiTestCase() { + + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + private lateinit var testScope: TestScope + + private val fingerprintRepository = FakeFingerprintPropertyRepository() + + private lateinit var interactor: SideFpsOverlayInteractor + + @Before + fun setup() { + testScope = TestScope(StandardTestDispatcher()) + interactor = SideFpsOverlayInteractorImpl(fingerprintRepository) + } + + @Test + fun testGetOverlayOffsets() = + testScope.runTest { + fingerprintRepository.setProperties( + sensorId = 1, + strength = SensorStrength.STRONG, + sensorType = FingerprintSensorType.REAR, + sensorLocations = + mapOf( + "" to + SensorLocationInternal( + "" /* displayId */, + 540 /* sensorLocationX */, + 1636 /* sensorLocationY */, + 130 /* sensorRadius */ + ), + "display_id_1" to + SensorLocationInternal( + "display_id_1" /* displayId */, + 100 /* sensorLocationX */, + 300 /* sensorLocationY */, + 20 /* sensorRadius */ + ) + ) + ) + + var offsets = interactor.getOverlayOffsets("display_id_1") + assertThat(offsets.displayId).isEqualTo("display_id_1") + assertThat(offsets.sensorLocationX).isEqualTo(100) + assertThat(offsets.sensorLocationY).isEqualTo(300) + assertThat(offsets.sensorRadius).isEqualTo(20) + + offsets = interactor.getOverlayOffsets("invalid_display_id") + assertThat(offsets.displayId).isEqualTo("") + assertThat(offsets.sensorLocationX).isEqualTo(540) + assertThat(offsets.sensorLocationY).isEqualTo(1636) + assertThat(offsets.sensorRadius).isEqualTo(130) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java index a1d4fb4dd583..69d8d0b6f091 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java @@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.complication.ComplicationLayoutEngine.Margins; import com.android.systemui.touch.TouchInsetManager; import org.junit.Before; @@ -42,6 +43,7 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.List; +import java.util.Random; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -54,6 +56,14 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Mock TouchInsetManager.TouchInsetSession mTouchSession; + ComplicationLayoutEngine createComplicationLayoutEngine() { + return createComplicationLayoutEngine(0); + } + + ComplicationLayoutEngine createComplicationLayoutEngine(int spacing) { + return new ComplicationLayoutEngine(mLayout, spacing, 0, 0, 0, 0, mTouchSession, 0, 0); + } + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -104,6 +114,73 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { engine.addComplication(info.id, info.view, info.lp, info.category); } + @Test + public void testCombineMargins() { + final Random rand = new Random(); + final Margins margins1 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), + rand.nextInt()); + final Margins margins2 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(), + rand.nextInt()); + final Margins combined = Margins.combine(margins1, margins2); + assertThat(margins1.start + margins2.start).isEqualTo(combined.start); + assertThat(margins1.top + margins2.top).isEqualTo(combined.top); + assertThat(margins1.end + margins2.end).isEqualTo(combined.end); + assertThat(margins1.bottom + margins2.bottom).isEqualTo(combined.bottom); + } + + @Test + public void testComplicationMarginPosition() { + final Random rand = new Random(); + final int startMargin = rand.nextInt(); + final int topMargin = rand.nextInt(); + final int endMargin = rand.nextInt(); + final int bottomMargin = rand.nextInt(); + final int spacing = rand.nextInt(); + + final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, spacing, + startMargin, topMargin, endMargin, bottomMargin, mTouchSession, 0, 0); + + final ViewInfo firstViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_DOWN, + 0), + Complication.CATEGORY_SYSTEM, + mLayout); + + addComplication(engine, firstViewInfo); + firstViewInfo.clearInvocations(); + + final ViewInfo secondViewInfo = new ViewInfo( + new ComplicationLayoutParams( + 100, + 100, + ComplicationLayoutParams.POSITION_TOP + | ComplicationLayoutParams.POSITION_END, + ComplicationLayoutParams.DIRECTION_DOWN, + 0), + Complication.CATEGORY_STANDARD, + mLayout); + + addComplication(engine, secondViewInfo); + + + // The first added view should have margins from both directions from the corner position. + verifyChange(firstViewInfo, false, lp -> { + assertThat(lp.topMargin).isEqualTo(topMargin); + assertThat(lp.getMarginEnd()).isEqualTo(endMargin); + }); + + // The second view should be spaced below the first view and have the side end margin. + verifyChange(secondViewInfo, false, lp -> { + assertThat(lp.topMargin).isEqualTo(spacing); + assertThat(lp.getMarginEnd()).isEqualTo(endMargin); + }); + } + /** * Makes sure the engine properly places a view within the {@link ConstraintLayout}. */ @@ -120,8 +197,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { Complication.CATEGORY_STANDARD, mLayout); - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); addComplication(engine, firstViewInfo); // Ensure the view is added to the top end corner @@ -148,8 +224,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { Complication.CATEGORY_STANDARD, mLayout); - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); addComplication(engine, firstViewInfo); // Ensure the view is added to the top end corner @@ -165,8 +240,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { */ @Test public void testDirectionLayout() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -214,8 +288,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { */ @Test public void testPositionLayout() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -302,8 +375,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testDefaultMargin() { final int margin = 5; - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(margin); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -379,8 +451,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { public void testComplicationMargin() { final int defaultMargin = 5; final int complicationMargin = 10; - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(defaultMargin); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -446,8 +517,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testWidthConstraint() { final int maxWidth = 20; - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo viewStartDirection = new ViewInfo( new ComplicationLayoutParams( @@ -495,8 +565,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testHeightConstraint() { final int maxHeight = 20; - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo viewUpDirection = new ViewInfo( new ComplicationLayoutParams( @@ -543,8 +612,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { */ @Test public void testConstraintNotSetWhenNotSpecified() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo view = new ViewInfo( new ComplicationLayoutParams( @@ -572,8 +640,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { */ @Test public void testRemoval() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -619,8 +686,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { */ @Test public void testDoubleRemoval() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo firstViewInfo = new ViewInfo( new ComplicationLayoutParams( @@ -649,8 +715,7 @@ public class ComplicationLayoutEngineTest extends SysuiTestCase { @Test public void testGetViews() { - final ComplicationLayoutEngine engine = - new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0); + final ComplicationLayoutEngine engine = createComplicationLayoutEngine(); final ViewInfo topEndView = new ViewInfo( new ComplicationLayoutParams( diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java index 286972db57a3..a23e9e40959a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java @@ -112,7 +112,7 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { ComplicationLayoutParams.POSITION_TOP, ComplicationLayoutParams.DIRECTION_DOWN, 3); - assertThat(params.getMargin(10) == 10).isTrue(); + assertThat(params.getDirectionalSpacing(10) == 10).isTrue(); } /** @@ -127,7 +127,7 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { ComplicationLayoutParams.DIRECTION_DOWN, 3, 10); - assertThat(params.getMargin(5) == 10).isTrue(); + assertThat(params.getDirectionalSpacing(5) == 10).isTrue(); } /** @@ -148,7 +148,7 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { assertThat(copy.getDirection() == params.getDirection()).isTrue(); assertThat(copy.getPosition() == params.getPosition()).isTrue(); assertThat(copy.getWeight() == params.getWeight()).isTrue(); - assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue(); + assertThat(copy.getDirectionalSpacing(0) == params.getDirectionalSpacing(1)).isTrue(); assertThat(copy.getConstraint() == params.getConstraint()).isTrue(); assertThat(copy.height == params.height).isTrue(); assertThat(copy.width == params.width).isTrue(); @@ -171,7 +171,7 @@ public class ComplicationLayoutParamsTest extends SysuiTestCase { assertThat(copy.getDirection() == params.getDirection()).isTrue(); assertThat(copy.getPosition() == params.getPosition()).isTrue(); assertThat(copy.getWeight() == params.getWeight()).isTrue(); - assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue(); + assertThat(copy.getDirectionalSpacing(1) == params.getDirectionalSpacing(1)).isTrue(); assertThat(copy.height == params.height).isTrue(); assertThat(copy.width == params.width).isTrue(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 3552399586a3..494e230947b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -301,6 +301,22 @@ public class DozeTriggersTest extends SysuiTestCase { } @Test + public void test_onSensor_tap() { + mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 200, null); + + verify(mHost).onSlpiTap(100, 200); + verify(mMachine).wakeUp(DozeLog.REASON_SENSOR_TAP); + } + + @Test + public void test_onSensor_double_tap() { + mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP, 100, 200, null); + + verify(mHost).onSlpiTap(100, 200); + verify(mMachine).wakeUp(DozeLog.REASON_SENSOR_DOUBLE_TAP); + } + + @Test public void testPickupGestureDroppedKeyguardOccluded() { // GIVEN device is in doze (screen blank, but running doze sensors) when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); 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 d4c2bafe016f..83c89f1e6855 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -58,6 +58,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardDisplayManager; import com.android.keyguard.KeyguardSecurityView; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.mediator.ScreenOnCoordinator; import com.android.systemui.DejankUtils; import com.android.systemui.SysuiTestCase; @@ -98,6 +99,8 @@ import com.android.systemui.util.time.FakeSystemClock; 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.MockitoAnnotations; @@ -141,6 +144,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock AuthController mAuthController; private @Mock ShadeExpansionStateManager mShadeExpansionStateManager; private @Mock ShadeWindowLogger mShadeWindowLogger; + private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback> + mKeyguardUpdateMonitorCallbackCaptor; private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake(); private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); @@ -179,6 +184,24 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() { + // GIVEN keyguard is not enabled and isn't showing + mViewMediator.onSystemReady(); + mViewMediator.setKeyguardEnabled(false); + TestableLooper.get(this).processAllMessages(); + captureKeyguardUpdateMonitorCallback(); + assertFalse(mViewMediator.isShowingAndNotOccluded()); + + // WHEN lockdown occurs + when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true); + mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0); + + // THEN keyguard is shown + TestableLooper.get(this).processAllMessages(); + assertTrue(mViewMediator.isShowingAndNotOccluded()); + } + + @Test public void testOnGoingToSleep_UpdatesKeyguardGoingAway() { mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER); verify(mUpdateMonitor).dispatchKeyguardGoingAway(false); @@ -602,4 +625,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); } + + private void captureKeyguardUpdateMonitorCallback() { + verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index d73c2c76272e..e61620beeff3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -530,7 +530,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( state = WakefulnessState.STARTING_TO_SLEEP, - isWakingUpOrAwake = false, lastWakeReason = WakeSleepReason.OTHER, lastSleepReason = WakeSleepReason.OTHER, ) @@ -545,7 +544,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( state = WakefulnessState.ASLEEP, - isWakingUpOrAwake = false, lastWakeReason = WakeSleepReason.OTHER, lastSleepReason = WakeSleepReason.OTHER, ) @@ -682,7 +680,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( WakefulnessState.STARTING_TO_SLEEP, - isWakingUpOrAwake = false, lastWakeReason = WakeSleepReason.POWER_BUTTON, lastSleepReason = WakeSleepReason.POWER_BUTTON ) @@ -708,7 +705,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( WakefulnessState.ASLEEP, - isWakingUpOrAwake = false, lastWakeReason = WakeSleepReason.POWER_BUTTON, lastSleepReason = WakeSleepReason.POWER_BUTTON ) @@ -765,7 +761,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( state = WakefulnessState.STARTING_TO_SLEEP, - isWakingUpOrAwake = false, lastWakeReason = WakeSleepReason.OTHER, lastSleepReason = WakeSleepReason.OTHER, ) @@ -1006,7 +1001,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository.setWakefulnessModel( WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, - true, WakeSleepReason.OTHER, WakeSleepReason.OTHER ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt index 657ee20475d8..b3104b7de4b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt @@ -50,7 +50,6 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() { val testCoroutineScope = TestCoroutineScope() underTest = KeyguardBouncerRepositoryImpl( - viewMediatorCallback, systemClock, testCoroutineScope, bouncerLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 4b4c7e9d39f3..4b797cb1ba46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.common.shared.model.Position import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.doze.DozeHost import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener @@ -69,7 +68,6 @@ import org.mockito.MockitoAnnotations class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController - @Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var biometricUnlockController: BiometricUnlockController @@ -91,7 +89,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { underTest = KeyguardRepositoryImpl( statusBarStateController, - dozeHost, wakefulnessLifecycle, biometricUnlockController, keyguardStateController, @@ -262,42 +259,21 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Test fun isDozing() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDozing.onEach { latest = it }.launchIn(this) - - runCurrent() - val captor = argumentCaptor<DozeHost.Callback>() - verify(dozeHost).addCallback(captor.capture()) - - captor.value.onDozingChanged(true) - runCurrent() - assertThat(latest).isTrue() + underTest.setIsDozing(true) + assertThat(underTest.isDozing.value).isEqualTo(true) - captor.value.onDozingChanged(false) - runCurrent() - assertThat(latest).isFalse() - - job.cancel() - runCurrent() - verify(dozeHost).removeCallback(captor.value) + underTest.setIsDozing(false) + assertThat(underTest.isDozing.value).isEqualTo(false) } @Test fun isDozing_startsWithCorrectInitialValueForIsDozing() = testScope.runTest { - var latest: Boolean? = null + assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(null) - whenever(statusBarStateController.isDozing).thenReturn(true) - var job = underTest.isDozing.onEach { latest = it }.launchIn(this) - runCurrent() - assertThat(latest).isTrue() - job.cancel() - - whenever(statusBarStateController.isDozing).thenReturn(false) - job = underTest.isDozing.onEach { latest = it }.launchIn(this) - runCurrent() - assertThat(latest).isFalse() - job.cancel() + val expectedPoint = Point(100, 200) + underTest.setLastDozeTapToWakePosition(expectedPoint) + assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(expectedPoint) } @Test 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 c2195c7bc2c1..8611359adc71 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 @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.logging.TrustRepositoryLogger import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogcatEchoTracker @@ -48,12 +49,14 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class TrustRepositoryTest : SysuiTestCase() { @Mock private lateinit var trustManager: TrustManager - @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener> + @Captor private lateinit var listener: ArgumentCaptor<TrustManager.TrustListener> private lateinit var userRepository: FakeUserRepository private lateinit var testScope: TestScope private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0)) private lateinit var underTest: TrustRepository + private lateinit var isCurrentUserTrusted: FlowValue<Boolean?> + private lateinit var isCurrentUserTrustManaged: FlowValue<Boolean?> @Before fun setUp() { @@ -70,21 +73,90 @@ class TrustRepositoryTest : SysuiTestCase() { TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger) } + fun TestScope.init() { + runCurrent() + verify(trustManager).registerTrustListener(listener.capture()) + isCurrentUserTrustManaged = collectLastValue(underTest.isCurrentUserTrustManaged) + isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) + } + @Test - fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() = + fun isCurrentUserTrustManaged_whenItChanges_emitsLatestValue() = + testScope.runTest { + init() + + val currentUserId = users[0].id + userRepository.setSelectedUserInfo(users[0]) + + listener.value.onTrustManagedChanged(true, currentUserId) + assertThat(isCurrentUserTrustManaged()).isTrue() + + listener.value.onTrustManagedChanged(false, currentUserId) + + assertThat(isCurrentUserTrustManaged()).isFalse() + } + + @Test + fun isCurrentUserTrustManaged_isFalse_byDefault() = testScope.runTest { runCurrent() - verify(trustManager).registerTrustListener(listenerCaptor.capture()) - val listener = listenerCaptor.value + + assertThat(collectLastValue(underTest.isCurrentUserTrustManaged)()).isFalse() + } + + @Test + fun isCurrentUserTrustManaged_whenItChangesForDifferentUser_noops() = + testScope.runTest { + init() + userRepository.setSelectedUserInfo(users[0]) + + // current user's trust is managed. + listener.value.onTrustManagedChanged(true, users[0].id) + // some other user's trust is not managed. + listener.value.onTrustManagedChanged(false, users[1].id) + + assertThat(isCurrentUserTrustManaged()).isTrue() + } + + @Test + fun isCurrentUserTrustManaged_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() = + testScope.runTest { + init() + + userRepository.setSelectedUserInfo(users[0]) + listener.value.onTrustManagedChanged(true, users[0].id) + + userRepository.setSelectedUserInfo(users[1]) + + assertThat(isCurrentUserTrustManaged()).isFalse() + } + + @Test + fun isCurrentUserTrustManaged_itChangesFirstBeforeUserInfoChanges_emitsCorrectValue() = + testScope.runTest { + init() + userRepository.setSelectedUserInfo(users[1]) + + listener.value.onTrustManagedChanged(true, users[0].id) + assertThat(isCurrentUserTrustManaged()).isFalse() + + userRepository.setSelectedUserInfo(users[0]) + + assertThat(isCurrentUserTrustManaged()).isTrue() + } + + @Test + fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() = + testScope.runTest { + init() val currentUserId = users[0].id userRepository.setSelectedUserInfo(users[0]) - val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) - listener.onTrustChanged(true, false, currentUserId, 0, emptyList()) + listener.value.onTrustChanged(true, false, currentUserId, 0, emptyList()) assertThat(isCurrentUserTrusted()).isTrue() - listener.onTrustChanged(false, false, currentUserId, 0, emptyList()) + listener.value.onTrustChanged(false, false, currentUserId, 0, emptyList()) assertThat(isCurrentUserTrusted()).isFalse() } @@ -102,16 +174,14 @@ class TrustRepositoryTest : SysuiTestCase() { @Test fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() = testScope.runTest { - runCurrent() - verify(trustManager).registerTrustListener(listenerCaptor.capture()) + init() + userRepository.setSelectedUserInfo(users[0]) - val listener = listenerCaptor.value - val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) // current user is trusted. - listener.onTrustChanged(true, true, users[0].id, 0, emptyList()) + listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList()) // some other user is not trusted. - listener.onTrustChanged(false, false, users[1].id, 0, emptyList()) + listener.value.onTrustChanged(false, false, users[1].id, 0, emptyList()) assertThat(isCurrentUserTrusted()).isTrue() } @@ -119,29 +189,24 @@ class TrustRepositoryTest : SysuiTestCase() { @Test fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() = testScope.runTest { - runCurrent() - verify(trustManager).registerTrustListener(listenerCaptor.capture()) - val listener = listenerCaptor.value + init() userRepository.setSelectedUserInfo(users[0]) - val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) - listener.onTrustChanged(true, true, users[0].id, 0, emptyList()) + listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList()) assertThat(isCurrentUserTrusted()).isTrue() - listener.onTrustChanged(false, true, users[0].id, 0, emptyList()) + listener.value.onTrustChanged(false, true, users[0].id, 0, emptyList()) assertThat(isCurrentUserTrusted()).isFalse() } @Test fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() = testScope.runTest { - runCurrent() - verify(trustManager).registerTrustListener(listenerCaptor.capture()) - val listener = listenerCaptor.value + init() + userRepository.setSelectedUserInfo(users[0]) - listener.onTrustChanged(true, true, users[0].id, 0, emptyList()) + listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList()) - val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) userRepository.setSelectedUserInfo(users[1]) assertThat(isCurrentUserTrusted()).isFalse() @@ -150,12 +215,9 @@ class TrustRepositoryTest : SysuiTestCase() { @Test fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() = testScope.runTest { - runCurrent() - verify(trustManager).registerTrustListener(listenerCaptor.capture()) - val listener = listenerCaptor.value - val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted) + init() - listener.onTrustChanged(true, true, users[0].id, 0, emptyList()) + listener.value.onTrustChanged(true, true, users[0].id, 0, emptyList()) assertThat(isCurrentUserTrusted()).isFalse() userRepository.setSelectedUserInfo(users[0]) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 2180a8f19636..ca6b8d51e9e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.keyguard.ViewMediatorCallback import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository @@ -39,7 +38,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -62,7 +60,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) bouncerRepository = KeyguardBouncerRepositoryImpl( - mock(ViewMediatorCallback::class.java), FakeSystemClock(), TestCoroutineScope(), bouncerLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 4b7c641abf1e..3336e3b21180 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -313,7 +313,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Test fun quickAffordance_bottomStartAffordanceHiddenWhileDozing() = testScope.runTest { - repository.setDozing(true) + repository.setIsDozing(true) homeControls.setState( KeyguardQuickAffordanceConfig.LockScreenState.Visible( icon = ICON, @@ -348,7 +348,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() = testScope.runTest { repository.setKeyguardShowing(false) - repository.setDozing(true) + repository.setIsDozing(true) val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS homeControls.setState( KeyguardQuickAffordanceConfig.LockScreenState.Visible( @@ -623,7 +623,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { testScope.runTest { featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) dockManager.setIsDocked(false) - val firstUseLongPress by collectLastValue (underTest.useLongPress()) + val firstUseLongPress by collectLastValue(underTest.useLongPress()) runCurrent() assertThat(firstUseLongPress).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 344df0acc409..603f199b468b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -871,7 +871,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private fun startingToWake() = WakefulnessModel( WakefulnessState.STARTING_TO_WAKE, - true, WakeSleepReason.OTHER, WakeSleepReason.OTHER ) @@ -879,7 +878,6 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private fun startingToSleep() = WakefulnessModel( WakefulnessState.STARTING_TO_SLEEP, - true, WakeSleepReason.OTHER, WakeSleepReason.OTHER ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 5eec8a88dd14..69d43af60321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -505,9 +505,9 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { val value = collectLastValue(underTest.isOverlayContainerVisible) assertThat(value()).isTrue() - repository.setDozing(true) + repository.setIsDozing(true) assertThat(value()).isFalse() - repository.setDozing(false) + repository.setIsDozing(false) assertThat(value()).isTrue() } 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 21a7a340aa80..425d0bc47a4e 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 @@ -39,6 +39,7 @@ import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.View; +import androidx.annotation.NonNull; import androidx.test.filters.MediumTest; import com.android.internal.logging.UiEventLogger; @@ -64,6 +65,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Consumer; @MediumTest @RunWith(AndroidTestingRunner.class) @@ -89,7 +91,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class); private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class); - private final MediaDescription mMediaDescription = mock(MediaDescription.class); + private final MediaDescription mMediaDescription = mock(MediaDescription.class); private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock( NearbyMediaDevicesManager.class); private final AudioManager mAudioManager = mock(AudioManager.class); @@ -102,6 +104,11 @@ public class MediaOutputDialogTest extends SysuiTestCase { private MediaOutputController mMediaOutputController; private final List<String> mFeatures = new ArrayList<>(); + @Override + protected boolean shouldFailOnLeakedReceiver() { + return true; + } + @Before public void setUp() { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); @@ -120,8 +127,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags); mMediaOutputController.mLocalMediaManager = mLocalMediaManager; - mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); + mMediaOutputDialog = makeTestDialog(mMediaOutputController); mMediaOutputDialog.show(); when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice); @@ -130,7 +136,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { @After public void tearDown() { - mMediaOutputDialog.dismissDialog(); + mMediaOutputDialog.dismiss(); } @Test @@ -311,11 +317,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { MediaOutputController mockMediaOutputController = mock(MediaOutputController.class); when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -328,11 +332,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true); when(mockMediaOutputController.isPlaying()).thenReturn(true); when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + withTestDialog(mockMediaOutputController, testDialog -> { + assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText); + }); } @Test @@ -341,11 +343,9 @@ public class MediaOutputDialogTest extends SysuiTestCase { when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false); when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null); when(mockMediaOutputController.isPlaying()).thenReturn(false); - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mockMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.onStopButtonClick(); + withTestDialog(mockMediaOutputController, testDialog -> { + testDialog.onStopButtonClick(); + }); verify(mockMediaOutputController).releaseSession(); } @@ -354,13 +354,22 @@ public class MediaOutputDialogTest extends SysuiTestCase { // Check the visibility metric logging by creating a new MediaOutput dialog, // and verify if the calling times increases. public void onCreate_ShouldLogVisibility() { - MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender, - mMediaOutputController, mUiEventLogger); - testDialog.show(); - - testDialog.dismissDialog(); + withTestDialog(mMediaOutputController, testDialog -> {}); verify(mUiEventLogger, times(2)) .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW); } + + @NonNull + private MediaOutputDialog makeTestDialog(MediaOutputController controller) { + return new MediaOutputDialog(mContext, false, mBroadcastSender, + controller, mUiEventLogger); + } + + private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) { + MediaOutputDialog testDialog = makeTestDialog(controller); + testDialog.show(); + c.accept(testDialog); + testDialog.dismiss(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index e4d8b2598fe3..810ab344e7d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -145,7 +145,8 @@ public class QSTileHostTest extends SysuiTestCase { mMainExecutor = new FakeExecutor(new FakeSystemClock()); mSharedPreferencesByUser = new SparseArray<>(); - when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) + when(mTileLifecycleManagerFactory + .create(any(Intent.class), any(UserHandle.class))) .thenReturn(mTileLifecycleManager); when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) .thenAnswer((Answer<SharedPreferences>) invocation -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 2e6b0cf7b0a8..67587e3a8914 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -60,6 +60,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -81,6 +83,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; + private FakeExecutor mExecutor; private HandlerThread mThread; private Handler mHandler; private TileLifecycleManager mStateManager; @@ -109,12 +112,14 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread = new HandlerThread("TestThread"); mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); + mExecutor = new FakeExecutor(new FakeSystemClock()); mStateManager = new TileLifecycleManager(mHandler, mWrappedContext, mock(IQSService.class), mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); } @After @@ -152,7 +157,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testBind() { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); } @@ -160,7 +166,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testPackageReceiverExported() throws Exception { // Make sure that we register a receiver setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); IntentFilter filter = mWrappedContext.mLastIntentFilter; assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_ADDED)); assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)); @@ -170,14 +177,17 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testUnbind() { - mStateManager.setBindService(true); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); } @Test public void testTileServiceCallbacks() throws Exception { - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.onTileAdded(); verify(mMockTileService).onTileAdded(); mStateManager.onStartListening(); @@ -193,7 +203,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { @Test public void testAddedBeforeBind() throws Exception { mStateManager.onTileAdded(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -203,7 +214,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testListeningBeforeBind() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -215,7 +227,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onClick(null); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); verify(mMockTileService).onTileAdded(); @@ -228,10 +241,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onStartListening(); } @@ -242,10 +257,12 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onStartListening(); mStateManager.onClick(null); mStateManager.onStopListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); verifyBind(1); - mStateManager.setBindService(false); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); assertFalse(mContext.isBound(mTileServiceComponentName)); verify(mMockTileService, never()).onClick(null); } @@ -255,7 +272,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mStateManager.onTileAdded(); mStateManager.onStartListening(); setPackageEnabled(false); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); // Package not available, not yet created. verifyBind(0); @@ -267,18 +285,19 @@ public class TileLifecycleManagerTest extends SysuiTestCase { Intent.ACTION_PACKAGE_CHANGED, Uri.fromParts( "package", mTileServiceComponentName.getPackageName(), null))); + mExecutor.runAllReady(); verifyBind(1); } @Test public void testKillProcess() throws Exception { mStateManager.onStartListening(); - mStateManager.setBindService(true); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); mStateManager.setBindRetryDelay(0); + mExecutor.runAllReady(); mStateManager.onServiceDisconnected(mTileServiceComponentName); - - // Guarantees mHandler has processed all messages. - assertTrue(mHandler.runWithScissors(()->{}, TEST_FAIL_TIMEOUT)); + mExecutor.runAllReady(); // Two calls: one for the first bind, one for the restart. verifyBind(2); @@ -299,9 +318,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); @@ -318,9 +339,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_WAIVE_PRIORITY; @@ -337,9 +360,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mMockPackageManagerAdapter, mMockBroadcastDispatcher, mTileServiceIntent, - mUser); + mUser, + mExecutor); - manager.setBindService(true); + manager.executeSetBindService(true); + mExecutor.runAllReady(); int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 9ca7a8521e95..28331bbfafb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -187,7 +187,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(true); ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(1)).setBindService(captor.capture()); + verify(mTileLifecycle, times(1)).executeSetBindService(captor.capture()); assertTrue((boolean) captor.getValue()); mTileServiceManager.setBindRequested(false); @@ -198,7 +198,7 @@ public class TileServiceManagerTest extends SysuiTestCase { mTileServiceManager.setBindAllowed(false); captor = ArgumentCaptor.forClass(Boolean.class); - verify(mTileLifecycle, times(2)).setBindService(captor.capture()); + verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture()); assertFalse((boolean) captor.getValue()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 12b5656725eb..4bc16a52f8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -48,6 +48,9 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Assert; @@ -118,7 +121,8 @@ public class TileServicesTest extends SysuiTestCase { mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher, mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController, - mPanelInteractor, mCustomTileAddedRepository); + mPanelInteractor, mCustomTileAddedRepository, + new FakeExecutor(new FakeSystemClock())); } @After @@ -297,10 +301,10 @@ public class TileServicesTest extends SysuiTestCase { BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, StatusBarIconController statusBarIconController, PanelInteractor panelInteractor, - CustomTileAddedRepository customTileAddedRepository) { + CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, commandQueue, statusBarIconController, panelInteractor, - customTileAddedRepository); + customTileAddedRepository, executor); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index b089e380304d..b00ae399af21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -93,6 +93,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private static final String CARD_DESCRIPTION = "•••• 1234"; private static final Icon CARD_IMAGE = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet); private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET) @@ -120,6 +122,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private QuickAccessWalletController mController; + @Mock + private Icon mCardImage; @Captor ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; @@ -142,6 +146,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true); when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true); when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient); + when(mCardImage.getType()).thenReturn(Icon.TYPE_URI); + when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null); mTile = new QuickAccessWalletTile( mHost, @@ -382,6 +388,28 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { } @Test + public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() { + when(mKeyguardStateController.isUnlocked()).thenReturn(true); + + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + WalletCard walletCard = + new WalletCard.Builder( + CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build(); + GetWalletCardsResponse response = + new GetWalletCardsResponse(Collections.singletonList(walletCard), 0); + + mTile.handleSetListening(true); + + verify(mController).queryWalletCards(mCallbackCaptor.capture()); + + mCallbackCaptor.getValue().onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + assertNull(mTile.getState().sideViewCustomDrawable); + } + + @Test public void testQueryCards_noCards_notUpdateSideViewDrawable() { setUpWalletCard(/* hasCard= */ false); 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 new file mode 100644 index 000000000000..1cdaec0c6581 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/Fakes.kt @@ -0,0 +1,51 @@ +/* + * 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 new file mode 100644 index 000000000000..9e264db845e1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -0,0 +1,139 @@ +/* + * 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(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.data.repository + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +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.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class SceneContainerRepositoryTest : SysuiTestCase() { + + @Test + fun allSceneKeys() { + val underTest = fakeSceneContainerRepository() + assertThat(underTest.allSceneKeys("container1")) + .isEqualTo( + listOf( + SceneKey.QuickSettings, + SceneKey.Shade, + SceneKey.LockScreen, + SceneKey.Bouncer, + SceneKey.Gone, + ) + ) + } + + @Test(expected = IllegalStateException::class) + fun allSceneKeys_noSuchContainer_throws() { + val underTest = fakeSceneContainerRepository() + underTest.allSceneKeys("nonExistingContainer") + } + + @Test + fun currentScene() = runTest { + val underTest = fakeSceneContainerRepository() + val currentScene by collectLastValue(underTest.currentScene("container1")) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen)) + + underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) + } + + @Test(expected = IllegalStateException::class) + fun currentScene_noSuchContainer_throws() { + val underTest = fakeSceneContainerRepository() + underTest.currentScene("nonExistingContainer") + } + + @Test(expected = IllegalStateException::class) + fun setCurrentScene_noSuchContainer_throws() { + val underTest = fakeSceneContainerRepository() + underTest.setCurrentScene("nonExistingContainer", SceneModel(SceneKey.Shade)) + } + + @Test(expected = IllegalStateException::class) + fun setCurrentScene_noSuchSceneInContainer_throws() { + val underTest = + fakeSceneContainerRepository( + setOf( + fakeSceneContainerConfig("container1"), + fakeSceneContainerConfig( + "container2", + listOf(SceneKey.QuickSettings, SceneKey.LockScreen) + ), + ) + ) + underTest.setCurrentScene("container2", SceneModel(SceneKey.Shade)) + } + + @Test + fun isVisible() = runTest { + val underTest = fakeSceneContainerRepository() + val isVisible by collectLastValue(underTest.isVisible("container1")) + assertThat(isVisible).isTrue() + + underTest.setVisible("container1", false) + assertThat(isVisible).isFalse() + + underTest.setVisible("container1", true) + assertThat(isVisible).isTrue() + } + + @Test(expected = IllegalStateException::class) + fun isVisible_noSuchContainer_throws() { + val underTest = fakeSceneContainerRepository() + underTest.isVisible("nonExistingContainer") + } + + @Test(expected = IllegalStateException::class) + fun setVisible_noSuchContainer_throws() { + val underTest = fakeSceneContainerRepository() + underTest.setVisible("nonExistingContainer", false) + } + + @Test + fun sceneTransitionProgress() = runTest { + val underTest = fakeSceneContainerRepository() + val sceneTransitionProgress by + collectLastValue(underTest.sceneTransitionProgress("container1")) + assertThat(sceneTransitionProgress).isEqualTo(1f) + + underTest.setSceneTransitionProgress("container1", 0.1f) + assertThat(sceneTransitionProgress).isEqualTo(0.1f) + + underTest.setSceneTransitionProgress("container1", 0.9f) + assertThat(sceneTransitionProgress).isEqualTo(0.9f) + } + + @Test(expected = IllegalStateException::class) + fun sceneTransitionProgress_noSuchContainer_throws() { + val underTest = 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 new file mode 100644 index 000000000000..c5ce09246862 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -0,0 +1,78 @@ +/* + * 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(ExperimentalCoroutinesApi::class, ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.interactor + +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.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.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class SceneInteractorTest : SysuiTestCase() { + + private val underTest = + SceneInteractor( + repository = fakeSceneContainerRepository(), + ) + + @Test + fun allSceneKeys() { + assertThat(underTest.allSceneKeys("container1")).isEqualTo(fakeSceneKeys()) + } + + @Test + fun sceneTransitions() = runTest { + val currentScene by collectLastValue(underTest.currentScene("container1")) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.LockScreen)) + + underTest.setCurrentScene("container1", SceneModel(SceneKey.Shade)) + assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade)) + } + + @Test + fun sceneTransitionProgress() = runTest { + val progress by collectLastValue(underTest.sceneTransitionProgress("container1")) + assertThat(progress).isEqualTo(1f) + + underTest.setSceneTransitionProgress("container1", 0.55f) + assertThat(progress).isEqualTo(0.55f) + } + + @Test + fun isVisible() = runTest { + val isVisible by collectLastValue(underTest.isVisible("container1")) + assertThat(isVisible).isTrue() + + underTest.setVisible("container1", false) + assertThat(isVisible).isFalse() + + underTest.setVisible("container1", true) + assertThat(isVisible).isTrue() + } +} 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 new file mode 100644 index 000000000000..ab61ddddaeab --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -0,0 +1,74 @@ +/* + * 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(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.ui.viewmodel + +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.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.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class SceneContainerViewModelTest : SysuiTestCase() { + private val interactor = + SceneInteractor( + repository = fakeSceneContainerRepository(), + ) + private val underTest = + SceneContainerViewModel( + interactor = interactor, + containerName = "container1", + ) + + @Test + fun isVisible() = runTest { + val isVisible by collectLastValue(underTest.isVisible) + assertThat(isVisible).isTrue() + + interactor.setVisible("container1", false) + assertThat(isVisible).isFalse() + + interactor.setVisible("container1", true) + assertThat(isVisible).isTrue() + } + + @Test + fun allSceneKeys() { + assertThat(underTest.allSceneKeys).isEqualTo(fakeSceneKeys()) + } + + @Test + fun sceneTransition() = runTest { + val currentScene by collectLastValue(underTest.currentScene) + 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/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index c3f51233f59a..2fbe87158eba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -327,7 +327,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { // GIVEN: Keyguard is showing, not dozing, unseen notification is present keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setDozing(false) + keyguardRepository.setIsDozing(false) runKeyguardCoordinatorTest { val fakeEntry = NotificationEntryBuilder().build() collectionListener.onEntryAdded(fakeEntry) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index b6b28c9e4527..4a3080050a37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent import android.content.Intent import android.os.RemoteException +import android.os.UserHandle import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor @@ -102,6 +103,7 @@ class ActivityStarterImplTest : SysuiTestCase() { activityIntentHelper, mainExecutor, ) + whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER) } @Test @@ -150,11 +152,28 @@ class ActivityStarterImplTest : SysuiTestCase() { @Test fun postStartActivityDismissingKeyguard_intent_postsOnMain() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true) val intent = mock(Intent::class.java) underTest.postStartActivityDismissingKeyguard(intent, 0) assertThat(mainExecutor.numPending()).isEqualTo(1) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController).runPostCollapseRunnables() + } + + @Test + fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() { + whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) + val intent = mock(Intent::class.java) + + underTest.postStartActivityDismissingKeyguard(intent, 0) + mainExecutor.runAllReady() + + verify(deviceProvisionedController).isDeviceProvisioned + verify(shadeController, never()).runPostCollapseRunnables() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java index 163369f0412f..57037e0c9c63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java @@ -25,8 +25,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.graphics.Point; import android.os.PowerManager; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -42,6 +44,7 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.BurnInInteractor; +import com.android.systemui.keyguard.domain.interactor.DozeInteractor; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -95,6 +98,7 @@ public class DozeServiceHostTest extends SysuiTestCase { @Mock private DozeHost.Callback mCallback; @Mock private BurnInInteractor mBurnInInteractor; + @Mock private DozeInteractor mDozeInteractor; @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -104,7 +108,7 @@ public class DozeServiceHostTest extends SysuiTestCase { () -> mAssistManager, mDozeScrimController, mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController, mNotificationWakeUpCoordinator, - mAuthController, mNotificationIconAreaController, + mAuthController, mNotificationIconAreaController, mDozeInteractor, mBurnInInteractor); mDozeServiceHost.initialize( @@ -216,6 +220,19 @@ public class DozeServiceHostTest extends SysuiTestCase { assertFalse(mDozeServiceHost.isPulsePending()); verify(mDozeScrimController).pulseOutNow(); } + + @Test + public void onSlpiTap_calls_DozeInteractor() { + mDozeServiceHost.onSlpiTap(100, 200); + verify(mDozeInteractor).setLastTapToWakePosition(new Point(100, 200)); + } + + @Test + public void onSlpiTap_doesnt_pass_negative_values() { + mDozeServiceHost.onSlpiTap(-1, 200); + mDozeServiceHost.onSlpiTap(100, -2); + verifyZeroInteractions(mDozeInteractor); + } @Test public void dozeTimeTickSentTBurnInInteractor() { // WHEN dozeTimeTick diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index a50155628027..6a4b3c55f244 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -222,31 +222,103 @@ public class LightBarControllerTest extends SysuiTestCase { // Initial state is set when controller is set mLightBarController.setNavigationBar(mNavBarController); - verifyNavBarIconsDarkSetTo(false); + verifyNavBarIconsDark(false, /* didFireEvent= */ true); // Changing the color of the transparent scrim has no effect mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_LIGHT); - verifyNavBarIconsUnchanged(); // still light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); // Showing the notification shade with white scrim requires dark icons mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_LIGHT); - verifyNavBarIconsDarkSetTo(true); + verifyNavBarIconsDark(true, /* didFireEvent= */ true); // Expanded QS always provides a black background, so icons become light again mLightBarController.setQsExpanded(true); - verifyNavBarIconsDarkSetTo(false); + verifyNavBarIconsDark(false, /* didFireEvent= */ true); // Tapping the QS tile to change to dark theme has no effect in this state mLightBarController.setScrimState(ScrimState.UNLOCKED, 1f, COLORS_DARK); - verifyNavBarIconsUnchanged(); // still light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); // collapsing QS in dark mode doesn't affect button color mLightBarController.setQsExpanded(false); - verifyNavBarIconsUnchanged(); // still light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); // Closing the shade has no affect mLightBarController.setScrimState(ScrimState.UNLOCKED, 0f, COLORS_DARK); - verifyNavBarIconsUnchanged(); // still light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); + } + + @Test + public void navBarHasDarkIconsInLockedShade_lightMode() { + assumeTrue(testNewLightBarLogic()); // Only run in the new suite + + // On the locked shade QS in light mode buttons are light + mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_LIGHT); + mLightBarController.onNavigationBarAppearanceChanged( + 0, /* nbModeChanged = */ true, + MODE_TRANSPARENT, /* navbarColorManagedByIme = */ false); + verifyNavBarIconsUnchanged(); // no changes yet; not attached + + // Initial state is set when controller is set + mLightBarController.setNavigationBar(mNavBarController); + verifyNavBarIconsDark(true, /* didFireEvent= */ true); + } + + @Test + public void navBarHasLightIconsInLockedQs_lightMode() { + // GIVEN dark icons in locked shade in light mdoe + navBarHasDarkIconsInLockedShade_lightMode(); + // WHEN expanding QS + mLightBarController.setQsExpanded(true); + // THEN icons become light + verifyNavBarIconsDark(false, /* didFireEvent= */ true); + } + + @Test + public void navBarHasDarkIconsInBouncerOverQs_lightMode() { + // GIVEN that light icons in locked expanded QS + navBarHasLightIconsInLockedQs_lightMode(); + // WHEN device changes to bouncer + mLightBarController.setScrimState(ScrimState.BOUNCER, 1f, COLORS_LIGHT); + // THEN icons change to dark + verifyNavBarIconsDark(true, /* didFireEvent= */ true); + } + + @Test + public void navBarHasLightIconsInLockedShade_darkMode() { + assumeTrue(testNewLightBarLogic()); // Only run in the new suite + + // On the locked shade QS in light mode buttons are light + mLightBarController.setScrimState(ScrimState.SHADE_LOCKED, 1f, COLORS_DARK); + mLightBarController.onNavigationBarAppearanceChanged( + 0, /* nbModeChanged = */ true, + MODE_TRANSPARENT, /* navbarColorManagedByIme = */ false); + verifyNavBarIconsUnchanged(); // no changes yet; not attached + + // Initial state is set when controller is set + mLightBarController.setNavigationBar(mNavBarController); + verifyNavBarIconsDark(false, /* didFireEvent= */ true); + } + + @Test + public void navBarHasLightIconsInLockedQs_darkMode() { + // GIVEN light icons in the locked shade + navBarHasLightIconsInLockedShade_darkMode(); + // WHEN QS expands + mLightBarController.setQsExpanded(true); + // THEN icons stay light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); + } + + @Test + public void navBarHasLightIconsInBouncerOverQs_darkMode() { + // GIVEN that light icons in locked expanded QS + navBarHasLightIconsInLockedQs_darkMode(); + // WHEN device changes to bouncer + mLightBarController.setScrimState(ScrimState.BOUNCER, 1f, COLORS_DARK); + // THEN icons stay light + verifyNavBarIconsDark(false, /* didFireEvent= */ false); } private void verifyNavBarIconsUnchanged() { @@ -258,4 +330,14 @@ public class LightBarControllerTest extends SysuiTestCase { verify(mNavBarController, never()).setIconsDark(eq(!iconsDark), anyBoolean()); clearInvocations(mNavBarController); } + + private void verifyNavBarIconsDark(boolean iconsDark, boolean didFireEvent) { + if (didFireEvent) { + verifyNavBarIconsDarkSetTo(iconsDark); + } else { + verifyNavBarIconsUnchanged(); + mLightBarController.setNavigationBar(mNavBarController); + verifyNavBarIconsDarkSetTo(iconsDark); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 10efcd41019c..9b1d93b691c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; +import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateController; @@ -129,6 +130,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Mock private StatusBarIconController.DarkIconManager mIconManager; private FakeCollapsedStatusBarViewModel mCollapsedStatusBarViewModel; + private FakeCollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder; @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; @Mock @@ -612,6 +614,55 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { assertEquals(View.VISIBLE, getClockView().getVisibility()); } + @Test + public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() { + final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN a transition to dream has started + mCollapsedStatusBarViewBinder.getListener().onTransitionFromLockscreenToDreamStarted(); + when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN status icons should be invisible or gone, but certainly not VISIBLE. + assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertNotEquals(View.VISIBLE, getClockView().getVisibility()); + + // WHEN the transition has finished and dream is displaying + mockLockscreenToDreamTransitionFinished(); + // (This approximates "dream is displaying") + when(mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer()) + .thenReturn(true); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN the views still aren't visible because dream is hiding them + assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertNotEquals(View.VISIBLE, getClockView().getVisibility()); + + // WHEN dream has ended + when(mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer()) + .thenReturn(false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN the views can be visible again + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test + public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() { + final CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + // WHEN a transition to dream has started but we're *not* dreaming + mCollapsedStatusBarViewBinder.getListener().onTransitionFromLockscreenToDreamStarted(); + when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + // THEN the views are still visible + assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { MockitoAnnotations.initMocks(this); @@ -631,6 +682,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mShadeExpansionStateManager = new ShadeExpansionStateManager(); mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel(); + mCollapsedStatusBarViewBinder = new FakeCollapsedStatusBarViewBinder(); setUpNotificationIconAreaController(); return new CollapsedStatusBarFragment( @@ -644,6 +696,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mStatusBarIconController, mIconManagerFactory, mCollapsedStatusBarViewModel, + mCollapsedStatusBarViewBinder, mStatusBarHideIconsForBouncerManager, mKeyguardStateController, mShadeViewController, @@ -718,6 +771,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } } + private void mockLockscreenToDreamTransitionFinished() { + for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) { + listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN); + } + } + private CollapsedStatusBarFragment resumeAndGetFragment() { mFragments.dispatchResume(); processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 38c7432eb288..fd156d86c19e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -1112,6 +1112,33 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun carrierConfig_initialValueIsFetched() = + testScope.runTest { + + // Value starts out false + assertThat(underTest.defaultDataSubRatConfig.value.showAtLeast3G).isFalse() + + overrideResource(R.bool.config_showMin3G, true) + val configFromContext = MobileMappings.Config.readConfig(context) + assertThat(configFromContext.showAtLeast3G).isTrue() + + // WHEN the change event is fired + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive( + context, + Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) + ) + } + + // WHEN collection starts AFTER the broadcast is sent out + val latest by collectLastValue(underTest.defaultDataSubRatConfig) + + // THEN the config has the updated value + assertThat(latest!!.areEqual(configFromContext)).isTrue() + assertThat(latest!!.showAtLeast3G).isTrue() + } + + @Test fun activeDataChange_inSameGroup_emitsUnit() = testScope.runTest { val latest by collectLastValue(underTest.activeSubChangedInGroupEvent) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 5faed9d952c2..c8c24a770a7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -176,4 +177,139 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { job.cancel() } + + @Test + fun transitionFromLockscreenToDreamStartedEvent_started_emitted() = + testScope.runTest { + val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 0f, + TransitionState.STARTED, + ) + ) + + assertThat(emissions.size).isEqualTo(1) + } + + @Test + fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() = + testScope.runTest { + val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 0f, + TransitionState.STARTED, + ) + ) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 0f, + TransitionState.STARTED, + ) + ) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 0f, + TransitionState.STARTED, + ) + ) + + assertThat(emissions.size).isEqualTo(3) + } + + @Test + fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() = + testScope.runTest { + val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 0f, + TransitionState.STARTED, + ) + ) + assertThat(emissions.size).isEqualTo(1) + + // WHEN the transition progresses through its animation by going through the RUNNING + // step with increasing fractions + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = .1f, + TransitionState.RUNNING, + ) + ) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = .2f, + TransitionState.RUNNING, + ) + ) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = .3f, + TransitionState.RUNNING, + ) + ) + + // THEN the flow does not emit since the flow should only emit when the transition + // starts + assertThat(emissions.size).isEqualTo(1) + } + + @Test + fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() = + testScope.runTest { + val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.OCCLUDED, + value = 0f, + TransitionState.STARTED, + ) + ) + + assertThat(emissions).isEmpty() + } + + @Test + fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() = + testScope.runTest { + val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.LOCKSCREEN, + KeyguardState.DREAMING, + value = 1.0f, + TransitionState.FINISHED, + ) + ) + + assertThat(emissions).isEmpty() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt new file mode 100644 index 000000000000..2ee928fa6d17 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.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.statusbar.pipeline.shared.ui.viewmodel + +import android.view.View +import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder +import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener + +/** + * A fake view binder that can be used from Java tests. + * + * Since Java tests can't run tests within test scopes, we need to bypass the flows from + * [CollapsedStatusBarViewModel] and just trigger the listener directly. + */ +class FakeCollapsedStatusBarViewBinder : CollapsedStatusBarViewBinder { + var listener: StatusBarVisibilityChangeListener? = null + + override fun bind( + view: View, + viewModel: CollapsedStatusBarViewModel, + listener: StatusBarVisibilityChangeListener, + ) { + this.listener = listener + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt index cbf6637ad46b..88587b2db0f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel { override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false) + + override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt index 3db0ecc4e8df..a5e52a469f97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt @@ -113,20 +113,6 @@ class StylusUsiPowerStartableTest : SysuiTestCase() { } @Test - fun onStylusBluetoothConnected_refreshesNotification() { - startable.onStylusBluetoothConnected(STYLUS_DEVICE_ID, "ANY") - - verify(stylusUsiPowerUi, times(1)).refresh() - } - - @Test - fun onStylusBluetoothDisconnected_refreshesNotification() { - startable.onStylusBluetoothDisconnected(STYLUS_DEVICE_ID, "ANY") - - verify(stylusUsiPowerUi, times(1)).refresh() - } - - @Test fun onStylusUsiBatteryStateChanged_batteryPresentValidCapacity_refreshesNotification() { val batteryState = FixedCapacityBatteryState(0.1f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt index 572aca9c0f42..90821bdef0be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt @@ -45,6 +45,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doNothing import org.mockito.Mockito.inOrder import org.mockito.Mockito.never @@ -194,22 +195,14 @@ class StylusUsiPowerUiTest : SysuiTestCase() { } @Test - fun refresh_hasConnectedBluetoothStylus_cancelsNotification() { - whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0)) - - stylusUsiPowerUi.refresh() - - verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) - } - - @Test - fun refresh_hasConnectedBluetoothStylus_existingNotification_cancelsNotification() { + fun refresh_hasConnectedBluetoothStylus_existingNotification_doesNothing() { stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f)) whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(0)) + clearInvocations(notificationManager) stylusUsiPowerUi.refresh() - verify(notificationManager).cancel(R.string.stylus_battery_low_percentage) + verifyNoMoreInteractions(notificationManager) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 17b5e0546eca..09ac0e312e21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -16,6 +16,8 @@ package com.android.systemui.theme; +import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; + import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR; import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE; @@ -29,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -926,4 +929,38 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(), any()); } + + @Test + public void createDynamicOverlay_addsAllDynamicColors() { + // Trigger new wallpaper colors to generate an overlay + WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), + Color.valueOf(Color.BLUE), null); + mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM, + USER_SYSTEM); + ArgumentCaptor<FabricatedOverlay[]> themeOverlays = + ArgumentCaptor.forClass(FabricatedOverlay[].class); + + verify(mThemeOverlayApplier) + .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any()); + + FabricatedOverlay[] overlays = themeOverlays.getValue(); + FabricatedOverlay accents = overlays[0]; + FabricatedOverlay neutrals = overlays[1]; + FabricatedOverlay dynamic = overlays[2]; + + final int colorsPerPalette = 12; + + // Color resources were added for all 3 accent palettes + verify(accents, times(colorsPerPalette * 3)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // Color resources were added for all 2 neutral palettes + verify(neutrals, times(colorsPerPalette * 2)) + .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + // All dynamic colors were added twice: light and dark them + // All fixed colors were added once + verify(dynamic, times( + DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2 + + DynamicColors.FIXED_COLORS_MAPPED.size()) + ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index 62985060e7d4..d9ee08157c84 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -146,7 +146,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { fun onFolded_aodDisabled_doesNotLogLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) - keyguardRepository.setDozing(true) + keyguardRepository.setIsDozing(true) setAodEnabled(enabled = false) yield() @@ -163,7 +163,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { fun onFolded_aodEnabled_logsLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) - keyguardRepository.setDozing(true) + keyguardRepository.setIsDozing(true) setAodEnabled(enabled = true) yield() @@ -181,7 +181,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { fun onFolded_onScreenTurningOnInvokedTwice_doesNotLogLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) - keyguardRepository.setDozing(true) + keyguardRepository.setIsDozing(true) setAodEnabled(enabled = true) yield() @@ -203,7 +203,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) - keyguardRepository.setDozing(false) + keyguardRepository.setIsDozing(false) setAodEnabled(enabled = true) yield() @@ -214,7 +214,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { // Now enable dozing and trigger a second run through the aod animation code. It should // not rerun the animation - keyguardRepository.setDozing(true) + keyguardRepository.setIsDozing(true) yield() simulateScreenTurningOn() @@ -228,7 +228,7 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { fun onFolded_animationCancelled_doesNotLogLatency() = runBlocking(IMMEDIATE) { val job = underTest.listenForDozing(this) - keyguardRepository.setDozing(true) + keyguardRepository.setIsDozing(true) setAodEnabled(enabled = true) yield() diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index e06b43ac5aea..45a37cffa588 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -17,22 +17,28 @@ package com.android.systemui.volume; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; +import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.KeyguardManager; +import android.content.res.Configuration; import android.media.AudioManager; import android.os.SystemClock; import android.provider.DeviceConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.Gravity; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; @@ -53,7 +59,9 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.statusbar.policy.FakeConfigurationController; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -82,6 +90,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { View mDrawerNormal; private DeviceConfigProxyFake mDeviceConfigProxy; private FakeExecutor mExecutor; + private TestableLooper mTestableLooper; + private ConfigurationController mConfigurationController; + private int mOriginalOrientation; @Mock VolumeDialogController mVolumeDialogController; @@ -92,8 +103,6 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock DeviceProvisionedController mDeviceProvisionedController; @Mock - ConfigurationController mConfigurationController; - @Mock MediaOutputDialogFactory mMediaOutputDialogFactory; @Mock VolumePanelFactory mVolumePanelFactory; @@ -104,6 +113,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock CsdWarningDialog mCsdWarningDialog; + @Mock + DevicePostureController mPostureController; private final CsdWarningDialog.Factory mCsdWarningDialogFactory = new CsdWarningDialog.Factory() { @@ -119,9 +130,17 @@ public class VolumeDialogImplTest extends SysuiTestCase { getContext().addMockSystemService(KeyguardManager.class, mKeyguard); + mTestableLooper = TestableLooper.get(this); mDeviceConfigProxy = new DeviceConfigProxyFake(); mExecutor = new FakeExecutor(new FakeSystemClock()); + when(mPostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + mOriginalOrientation = mContext.getResources().getConfiguration().orientation; + + mConfigurationController = new FakeConfigurationController(); + mDialog = new VolumeDialogImpl( getContext(), mVolumeDialogController, @@ -135,8 +154,9 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDeviceConfigProxy, mExecutor, mCsdWarningDialogFactory, - mDumpManager - ); + mPostureController, + mTestableLooper.getLooper(), + mDumpManager); mDialog.init(0, null); State state = createShellState(); mDialog.onStateChangedH(state); @@ -227,6 +247,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class); verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any()); VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue(); + callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI); verify(mAccessibilityMgr).getRecommendedTimeoutMillis( VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS, @@ -371,11 +392,171 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mCsdWarningDialog).show(); } + @Test + public void ifPortraitHalfOpen_drawVerticallyTop() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0 , null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + // Call show() to trigger layout updates before verifying position + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifPortraitAndOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_PORTRAIT); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void ifLandscapeAndHalfOpen_drawCenterVertically() { + DevicePostureController devicePostureController = mock(DevicePostureController.class); + when(devicePostureController.getDevicePosture()) + .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED); + + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + devicePostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(devicePostureController).addCallback(any()); + dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); + mTestableLooper.processAllMessages(); // let dismiss() finish + + setOrientation(Configuration.ORIENTATION_LANDSCAPE); + + dialog.show(SHOW_REASON_UNKNOWN); + mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect + + int gravity = dialog.getWindowGravity(); + assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK); + } + + @Test + public void dialogInit_addsPostureControllerCallback() { + // init is already called in setup + verify(mPostureController).addCallback(any()); + } + + @Test + public void dialogDestroy_removesPostureControllerCallback() { + VolumeDialogImpl dialog = new VolumeDialogImpl( + getContext(), + mVolumeDialogController, + mAccessibilityMgr, + mDeviceProvisionedController, + mConfigurationController, + mMediaOutputDialogFactory, + mVolumePanelFactory, + mActivityStarter, + mInteractionJankMonitor, + mDeviceConfigProxy, + mExecutor, + mCsdWarningDialogFactory, + mPostureController, + mTestableLooper.getLooper(), + mDumpManager + ); + dialog.init(0, null); + + verify(mPostureController, never()).removeCallback(any()); + + dialog.destroy(); + + verify(mPostureController).removeCallback(any()); + } + + private void setOrientation(int orientation) { + Configuration config = new Configuration(); + config.orientation = orientation; + if (mConfigurationController != null) { + mConfigurationController.onConfigurationChanged(config); + } + } + @After public void teardown() { if (mDialog != null) { mDialog.clearInternalHandlerAfterTest(); } + setOrientation(mOriginalOrientation); + mTestableLooper.processAllMessages(); + reset(mPostureController); } /* diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 1ec4e8c48707..8bbd58dc8fe1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -102,7 +102,8 @@ public abstract class SysuiTestCase { mock(Executor.class), mock(DumpManager.class), mock(BroadcastDispatcherLogger.class), - mock(UserTracker.class)); + mock(UserTracker.class), + shouldFailOnLeakedReceiver()); mRealInstrumentation = InstrumentationRegistry.getInstrumentation(); Instrumentation inst = spy(mRealInstrumentation); @@ -141,6 +142,10 @@ public abstract class SysuiTestCase { mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator()); } + protected boolean shouldFailOnLeakedReceiver() { + return false; + } + @After public void SysuiTeardown() { if (mRealInstrumentation != null) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt index d9012a527726..2362a5241244 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt @@ -39,20 +39,21 @@ class FakeFingerprintPropertyRepository : FingerprintPropertyRepository { MutableStateFlow(FingerprintSensorType.UNKNOWN) override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow() - private val _sensorLocation: MutableStateFlow<SensorLocationInternal> = - MutableStateFlow(SensorLocationInternal.DEFAULT) - override val sensorLocation = _sensorLocation.asStateFlow() + private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> = + MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT)) + override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> = + _sensorLocations.asStateFlow() fun setProperties( sensorId: Int, strength: SensorStrength, sensorType: FingerprintSensorType, - sensorLocation: SensorLocationInternal + sensorLocations: Map<String, SensorLocationInternal> ) { _sensorId.value = sensorId _strength.value = strength _sensorType.value = sensorType - _sensorLocation.value = sensorLocation + _sensorLocations.value = sensorLocations _isInitialized.value = true } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt index ad086ff9c664..af940e4fa687 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestableContext import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger import com.android.systemui.dump.DumpManager import com.android.systemui.settings.UserTracker +import java.lang.IllegalStateException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executor @@ -37,7 +38,8 @@ class FakeBroadcastDispatcher( broadcastRunningExecutor: Executor, dumpManager: DumpManager, logger: BroadcastDispatcherLogger, - userTracker: UserTracker + userTracker: UserTracker, + private val shouldFailOnLeakedReceiver: Boolean ) : BroadcastDispatcher( context, @@ -85,6 +87,9 @@ class FakeBroadcastDispatcher( fun cleanUpReceivers(testName: String) { registeredReceivers.forEach { Log.i(testName, "Receiver not unregistered from dispatcher: $it") + if (shouldFailOnLeakedReceiver) { + throw IllegalStateException("Receiver not unregistered from dispatcher: $it") + } } registeredReceivers.clear() } 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 d4115900850f..fd8c4b81063d 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 @@ -54,7 +54,10 @@ class FakeKeyguardRepository : KeyguardRepository { override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded private val _isDozing = MutableStateFlow(false) - override val isDozing: Flow<Boolean> = _isDozing + override val isDozing: StateFlow<Boolean> = _isDozing + + private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null) + override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow() private val _isAodAvailable = MutableStateFlow(false) override val isAodAvailable: Flow<Boolean> = _isAodAvailable @@ -76,12 +79,7 @@ class FakeKeyguardRepository : KeyguardRepository { private val _wakefulnessModel = MutableStateFlow( - WakefulnessModel( - WakefulnessState.ASLEEP, - false, - WakeSleepReason.OTHER, - WakeSleepReason.OTHER - ) + WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER) ) override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel @@ -137,10 +135,14 @@ class FakeKeyguardRepository : KeyguardRepository { _isKeyguardOccluded.value = isOccluded } - fun setDozing(isDozing: Boolean) { + override fun setIsDozing(isDozing: Boolean) { _isDozing.value = isDozing } + override fun setLastDozeTapToWakePosition(position: Point) { + _lastDozeTapToWakePosition.value = position + } + fun setAodAvailable(isAodAvailable: Boolean) { _isAodAvailable.value = isAodAvailable } 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 f0dbc60cae1c..1340a47ab6de 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 @@ -31,10 +31,18 @@ class FakeTrustRepository : TrustRepository { override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = _isCurrentUserActiveUnlockAvailable.asStateFlow() + private val _isCurrentUserTrustManaged = MutableStateFlow(false) + override val isCurrentUserTrustManaged: StateFlow<Boolean> + get() = _isCurrentUserTrustManaged + fun setCurrentUserTrusted(trust: Boolean) { _isCurrentUserTrusted.value = trust } + fun setCurrentUserTrustManaged(value: Boolean) { + _isCurrentUserTrustManaged.value = value + } + fun setCurrentUserActiveUnlockAvailable(available: Boolean) { _isCurrentUserActiveUnlockAvailable.value = available } diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java index 6aca2fdc0f7f..496f4f6c6680 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java @@ -365,6 +365,11 @@ public class WallpaperBackupAgent extends BackupAgent { final int sysWhich = FLAG_SYSTEM | (lockImageStage.exists() ? 0 : FLAG_LOCK); try { + // First parse the live component name so that we know for logging if we care about + // logging errors with the image restore. + ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); + mSystemHasLiveComponent = wpService != null; + // It is valid for the imagery to be absent; it means that we were not permitted // to back up the original image on the source device, or there was no user-supplied // wallpaper image present. @@ -372,10 +377,10 @@ public class WallpaperBackupAgent extends BackupAgent { restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK); // And reset to the wallpaper service we should be using - ComponentName wpService = parseWallpaperComponent(infoStage, "wp"); updateWallpaperComponent(wpService, !lockImageStage.exists()); } catch (Exception e) { Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage()); + mEventLogger.onRestoreException(e); } finally { Slog.v(TAG, "Restore finished; clearing backup bookkeeping"); infoStage.delete(); @@ -399,12 +404,15 @@ public class WallpaperBackupAgent extends BackupAgent { // We have a live wallpaper and no static lock image, // allow live wallpaper to show "through" on lock screen. mWallpaperManager.clear(FLAG_LOCK); + mEventLogger.onLockLiveWallpaperRestored(wpService); } + mEventLogger.onSystemLiveWallpaperRestored(wpService); } else { // If we've restored a live wallpaper, but the component doesn't exist, // we should log it as an error so we can easily identify the problem // in reports from users if (wpService != null) { + // TODO(b/268471749): Handle delayed case applyComponentAtInstall(wpService, applyToLock); Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. " + " Will try to apply later"); @@ -424,13 +432,37 @@ public class WallpaperBackupAgent extends BackupAgent { try (FileInputStream in = new FileInputStream(stage)) { mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which); + + // And log the success + if ((which & FLAG_SYSTEM) > 0) { + mEventLogger.onSystemImageWallpaperRestored(); + } else { + mEventLogger.onLockImageWallpaperRestored(); + } } + } else { + logRestoreError(which, ERROR_NO_METADATA); } } else { Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath()); + logRestoreErrorIfNoLiveComponent(which, ERROR_NO_WALLPAPER); + } + } + + private void logRestoreErrorIfNoLiveComponent(int which, String error) { + if (mSystemHasLiveComponent) { + return; } + logRestoreError(which, error); } + private void logRestoreError(int which, String error) { + if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) { + mEventLogger.onSystemImageWallpaperRestoreFailed(error); + } else if ((which & FLAG_LOCK) == FLAG_LOCK) { + mEventLogger.onLockImageWallpaperRestoreFailed(error); + } + } private Rect parseCropHint(File wallpaperInfo, String sectionTag) { Rect cropHint = new Rect(); try (FileInputStream stream = new FileInputStream(wallpaperInfo)) { diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java index 64944b3ff54f..47c45ac7e775 100644 --- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java +++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java @@ -22,6 +22,7 @@ import android.app.backup.BackupManager; import android.app.backup.BackupRestoreEventLogger; import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType; import android.app.backup.BackupRestoreEventLogger.BackupRestoreError; +import android.content.ComponentName; import com.android.internal.annotations.VisibleForTesting; @@ -101,6 +102,39 @@ public class WallpaperEventLogger { logBackupFailureInternal(WALLPAPER_LIVE_LOCK, error); } + void onSystemImageWallpaperRestored() { + logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null); + } + + void onLockImageWallpaperRestored() { + logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null); + } + + void onSystemLiveWallpaperRestored(ComponentName wpService) { + logRestoreSuccessInternal(WALLPAPER_LIVE_SYSTEM, wpService); + } + + void onLockLiveWallpaperRestored(ComponentName wpService) { + logRestoreSuccessInternal(WALLPAPER_LIVE_LOCK, wpService); + } + + void onSystemImageWallpaperRestoreFailed(@BackupRestoreError String error) { + logRestoreFailureInternal(WALLPAPER_IMG_SYSTEM, error); + } + + void onLockImageWallpaperRestoreFailed(@BackupRestoreError String error) { + logRestoreFailureInternal(WALLPAPER_IMG_LOCK, error); + } + + void onSystemLiveWallpaperRestoreFailed(@BackupRestoreError String error) { + logRestoreFailureInternal(WALLPAPER_LIVE_SYSTEM, error); + } + + void onLockLiveWallpaperRestoreFailed(@BackupRestoreError String error) { + logRestoreFailureInternal(WALLPAPER_LIVE_LOCK, error); + } + + /** * Called when the whole backup flow is interrupted by an exception. @@ -117,6 +151,20 @@ public class WallpaperEventLogger { } } + /** + * Called when the whole restore flow is interrupted by an exception. + */ + void onRestoreException(Exception exception) { + String error = exception.getClass().getName(); + if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains( + WALLPAPER_LIVE_SYSTEM)) { + mLogger.logItemsRestoreFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error); + } + if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains( + WALLPAPER_LIVE_LOCK)) { + mLogger.logItemsRestoreFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error); + } + } private void logBackupSuccessInternal(@BackupRestoreDataType String which, @Nullable WallpaperInfo liveComponentWallpaperInfo) { mLogger.logItemsBackedUp(which, /* count */ 1); @@ -130,10 +178,30 @@ public class WallpaperEventLogger { mProcessedDataTypes.add(which); } + private void logRestoreSuccessInternal(@BackupRestoreDataType String which, + @Nullable ComponentName liveComponentWallpaperInfo) { + mLogger.logItemsRestored(which, /* count */ 1); + logRestoredLiveWallpaperNameIfPresent(which, liveComponentWallpaperInfo); + mProcessedDataTypes.add(which); + } + + private void logRestoreFailureInternal(@BackupRestoreDataType String which, + @BackupRestoreError String error) { + mLogger.logItemsRestoreFailed(which, /* count */ 1, error); + mProcessedDataTypes.add(which); + } + private void logLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType, WallpaperInfo wallpaperInfo) { if (wallpaperInfo != null) { mLogger.logBackupMetadata(wallpaperType, wallpaperInfo.getComponent().getClassName()); } } + + private void logRestoredLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType, + ComponentName wpService) { + if (wpService != null) { + mLogger.logRestoreMetadata(wallpaperType, wpService.getClassName()); + } + } } diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java index 89459f6e6772..58f6477ed082 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java @@ -24,6 +24,7 @@ import static com.android.wallpaperbackup.WallpaperBackupAgent.LOCK_WALLPAPER_ST import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE; import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE; +import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER; import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED; import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK; @@ -34,6 +35,8 @@ import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SY import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -55,12 +58,14 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.UserHandle; import android.service.wallpaper.WallpaperService; +import android.util.Xml; import androidx.test.InstrumentationRegistry; import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; import com.android.internal.content.PackageMonitor; +import com.android.modules.utils.TypedXmlSerializer; import com.android.wallpaperbackup.utils.ContextWithServiceOverrides; import org.junit.After; @@ -592,6 +597,123 @@ public class WallpaperBackupAgentTest { assertThat(result.getSuccessCount()).isEqualTo(1); } + @Test + public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception { + mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); + mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void testOnRestore_lockWallpaperImgSuccess_logsSuccess() throws Exception { + mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); + mockStagedWallpaperFile(LOCK_WALLPAPER_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void testOnRestore_systemWallpaperImgMissingAndNoLive_logsFailure() throws Exception { + mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); + mockStagedWallpaperFile(LOCK_WALLPAPER_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER); + + } + + @Test + public void testOnRestore_lockWallpaperImgMissingAndNoLive_logsFailure() throws Exception { + mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); + mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER); + } + + @Test + public void testOnRestore_wallpaperInfoMissing_logsFailure() throws Exception { + mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(ERROR_NO_METADATA); + } + + @Test + public void testOnRestore_imgMissingButWallpaperInfoHasLive_doesNotLogImg() throws Exception { + mockRestoredLiveWallpaperFile(); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult system = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + DataTypeResult lock = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(system).isNull(); + assertThat(lock).isNull(); + } + + @Test + public void testOnRestore_throwsException_logsErrors() throws Exception { + when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow( + new RuntimeException()); + mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE); + mockStagedWallpaperFile(WALLPAPER_INFO_STAGE); + mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD, + BackupAnnotations.OperationType.RESTORE); + + mWallpaperBackupAgent.onRestoreFinished(); + + DataTypeResult system = getLoggingResult(WALLPAPER_IMG_SYSTEM, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + DataTypeResult lock = getLoggingResult(WALLPAPER_IMG_LOCK, + mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults()); + assertThat(system).isNotNull(); + assertThat(system.getFailCount()).isEqualTo(1); + assertThat(system.getErrors()).containsKey(RuntimeException.class.getName()); + assertThat(lock).isNotNull(); + assertThat(lock.getFailCount()).isEqualTo(1); + assertThat(lock.getErrors()).containsKey(RuntimeException.class.getName()); + } + private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) { when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId); when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId); @@ -636,6 +758,27 @@ public class WallpaperBackupAgentTest { ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY)); } + private void mockStagedWallpaperFile(String location) throws Exception { + File wallpaperFile = new File(mContext.getFilesDir(), location); + wallpaperFile.createNewFile(); + } + + private void mockRestoredLiveWallpaperFile() throws Exception { + File wallpaperFile = new File(mContext.getFilesDir(), WALLPAPER_INFO_STAGE); + wallpaperFile.createNewFile(); + FileOutputStream fstream = new FileOutputStream(wallpaperFile, false); + TypedXmlSerializer out = Xml.resolveSerializer(fstream); + out.startDocument(null, true); + out.startTag(null, "wp"); + out.attribute(null, "component", + getFakeWallpaperInfo().getComponent().flattenToShortString()); + out.endTag(null, "wp"); + out.endDocument(); + fstream.flush(); + FileUtils.sync(fstream); + fstream.close(); + } + private WallpaperInfo getFakeWallpaperInfo() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java index 3816a3ccc1eb..383bf2f68217 100644 --- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java +++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java @@ -21,16 +21,14 @@ import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_SYS import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK; import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.WallpaperInfo; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupManager; import android.app.backup.BackupRestoreEventLogger; import android.content.Context; @@ -42,8 +40,6 @@ import android.service.wallpaper.WallpaperService; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; -import com.android.wallpaperbackup.utils.TestWallpaperService; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,8 +51,7 @@ import java.util.List; @RunWith(AndroidJUnit4.class) public class WallpaperEventLoggerTest { - @Mock - private BackupRestoreEventLogger mMockLogger; + private BackupRestoreEventLogger mEventLogger; @Mock private BackupManager mMockBackupManager; @@ -73,8 +68,8 @@ public class WallpaperEventLoggerTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mMockLogger); - when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mMockLogger); + when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mEventLogger); + when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mEventLogger); mWallpaperInfo = getWallpaperInfo(); mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent); @@ -82,115 +77,339 @@ public class WallpaperEventLoggerTest { @Test public void onSystemImgWallpaperBackedUp_logsSuccess() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); - verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_SYSTEM), eq(1)); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } @Test public void onLockImgWallpaperBackedUp_logsSuccess() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onLockImageWallpaperBackedUp(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); - verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_LOCK), eq(1)); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } @Test public void onSystemLiveWallpaperBackedUp_logsSuccess() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); - verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_SYSTEM), eq(1)); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } @Test public void onLockLiveWallpaperBackedUp_logsSuccess() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onLockLiveWallpaperBackedUp(mWallpaperInfo); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK); - verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_LOCK), eq(1)); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } @Test public void onImgWallpaperBackedUp_nullInfo_doesNotLogMetadata() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); - verify(mMockLogger, never()).logBackupMetadata(eq(WALLPAPER_IMG_SYSTEM), anyString()); + assertThat(result).isNotNull(); + assertThat(result.getMetadataHash()).isNull(); } @Test public void onLiveWallpaperBackedUp_logsMetadata() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); - verify(mMockLogger).logBackupMetadata(eq(WALLPAPER_LIVE_SYSTEM), - eq(TestWallpaperService.class.getName())); + assertThat(result).isNotNull(); + assertThat(result.getMetadataHash()).isNotNull(); } @Test public void onSystemImgWallpaperBackupFailed_logsFail() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemImageWallpaperBackupFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); - verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), eq(1), - eq(WALLPAPER_ERROR)); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); } @Test public void onLockImgWallpaperBackupFailed_logsFail() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onLockImageWallpaperBackupFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); - verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1), - eq(WALLPAPER_ERROR)); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); } @Test public void onSystemLiveWallpaperBackupFailed_logsFail() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onSystemLiveWallpaperBackupFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); - verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_SYSTEM), eq(1), - eq(WALLPAPER_ERROR)); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); } @Test public void onLockLiveWallpaperBackupFailed_logsFail() { + setUpLoggerForBackup(); + mWallpaperEventLogger.onLockLiveWallpaperBackupFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK); - verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_LOCK), eq(1), - eq(WALLPAPER_ERROR)); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); } @Test public void onWallpaperBackupException_someProcessed_doesNotLogErrorForProcessedType() { + setUpLoggerForBackup(); mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); mWallpaperEventLogger.onBackupException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); - verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(), - anyString()); + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(0); } @Test public void onWallpaperBackupException_someProcessed_logsErrorForUnprocessedType() { + setUpLoggerForBackup(); mWallpaperEventLogger.onSystemImageWallpaperBackedUp(); mWallpaperEventLogger.onBackupException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); - verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1), - eq(Exception.class.getName())); - + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); } @Test - public void onWallpaperBackupException_liveTypeProcessed_doesNotLogErrorForSameImgType() { + public void onWallpaperBackupException_liveTypeProcessed_doesNotLogForSameImgType() { + setUpLoggerForBackup(); mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo); mWallpaperEventLogger.onBackupException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNull(); + } + + @Test + public void onSystemImgWallpaperRestored_logsSuccess() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemImageWallpaperRestored(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void onLockImgWallpaperRestored_logsSuccess() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onLockImageWallpaperRestored(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); - verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(), - anyString()); + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); } + @Test + public void onSystemLiveWallpaperRestored_logsSuccess() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void onLockLiveWallpaperRestored_logsSuccess() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onLockLiveWallpaperRestored(mWallpaperInfo.getComponent()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK); + + assertThat(result).isNotNull(); + assertThat(result.getSuccessCount()).isEqualTo(1); + } + + @Test + public void onImgWallpaperRestored_nullInfo_doesNotLogMetadata() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemImageWallpaperRestored(); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getMetadataHash()).isNull(); + } + + + @Test + public void onLiveWallpaperRestored_logsMetadata() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getMetadataHash()).isNotNull(); + } + + + @Test + public void onSystemImgWallpaperRestoreFailed_logsFail() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemImageWallpaperRestoreFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); + } + + @Test + public void onLockImgWallpaperRestoreFailed_logsFail() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onLockImageWallpaperRestoreFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); + } + + + @Test + public void onSystemLiveWallpaperRestoreFailed_logsFail() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onSystemLiveWallpaperRestoreFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); + } + + @Test + public void onLockLiveWallpaperRestoreFailed_logsFail() { + setUpLoggerForRestore(); + + mWallpaperEventLogger.onLockLiveWallpaperRestoreFailed(WALLPAPER_ERROR); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR); + } + + + @Test + public void onWallpaperRestoreException_someProcessed_doesNotLogErrorForProcessedType() { + setUpLoggerForRestore(); + mWallpaperEventLogger.onSystemImageWallpaperRestored(); + + mWallpaperEventLogger.onRestoreException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(0); + } + + + @Test + public void onWallpaperRestoreException_someProcessed_logsErrorForUnprocessedType() { + setUpLoggerForRestore(); + mWallpaperEventLogger.onSystemImageWallpaperRestored(); + + mWallpaperEventLogger.onRestoreException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK); + + assertThat(result).isNotNull(); + assertThat(result.getFailCount()).isEqualTo(1); + } + + @Test + public void onWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() { + setUpLoggerForRestore(); + mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent()); + + mWallpaperEventLogger.onRestoreException(new Exception()); + BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM); + + assertThat(result).isNull(); + } + + private BackupRestoreEventLogger.DataTypeResult getLogsForType(String dataType) { + for (BackupRestoreEventLogger.DataTypeResult result : mEventLogger.getLoggingResults()) { + if ((result.getDataType()).equals(dataType)) { + return result; + } + } + return null; + } + + private void setUpLoggerForBackup() { + mEventLogger = new BackupRestoreEventLogger(BackupAnnotations.OperationType.BACKUP); + createEventLogger(); + } + + private void setUpLoggerForRestore() { + mEventLogger = new BackupRestoreEventLogger(BackupAnnotations.OperationType.RESTORE); + createEventLogger(); + } + + private void createEventLogger() { + when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mEventLogger); + when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mEventLogger); + + mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent); + } + + private WallpaperInfo getWallpaperInfo() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index 1298f638b01c..f31ca8178eb6 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -151,7 +151,6 @@ public class CameraExtensionsProxyService extends Service { (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) || EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX)); - private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>(); private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>(); private CameraManager mCameraManager; @@ -501,7 +500,6 @@ public class CameraExtensionsProxyService extends Service { if (cameraIds != null) { for (String cameraId : cameraIds) { CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId); - mCharacteristicsHashMap.put(cameraId, chars); Object thisClass = CameraCharacteristics.Key.class; Class<CameraCharacteristics.Key<?>> keyClass = (Class<CameraCharacteristics.Key<?>>)thisClass; @@ -554,6 +552,15 @@ public class CameraExtensionsProxyService extends Service { return ret; } + private static Map<String, CameraCharacteristics> getCharacteristicsMap( + Map<String, CameraMetadataNative> charsMap) { + HashMap<String, CameraCharacteristics> ret = new HashMap<>(); + for (Map.Entry<String, CameraMetadataNative> entry : charsMap.entrySet()) { + ret.put(entry.getKey(), new CameraCharacteristics(entry.getValue())); + } + return ret; + } + private static List<SizeList> initializeParcelable( Map<Integer, List<android.util.Size>> sizes) { if (sizes == null) { @@ -734,13 +741,15 @@ public class CameraExtensionsProxyService extends Service { } @Override - public boolean isExtensionAvailable(String cameraId) { - return mAdvancedExtender.isExtensionAvailable(cameraId, mCharacteristicsHashMap); + public boolean isExtensionAvailable(String cameraId, + Map<String, CameraMetadataNative> charsMapNative) { + return mAdvancedExtender.isExtensionAvailable(cameraId, + getCharacteristicsMap(charsMapNative)); } @Override - public void init(String cameraId) { - mAdvancedExtender.init(cameraId, mCharacteristicsHashMap); + public void init(String cameraId, Map<String, CameraMetadataNative> charsMapNative) { + mAdvancedExtender.init(cameraId, getCharacteristicsMap(charsMapNative)); } @Override @@ -1192,7 +1201,8 @@ public class CameraExtensionsProxyService extends Service { } @Override - public CameraSessionConfig initSession(String cameraId, OutputSurface previewSurface, + public CameraSessionConfig initSession(String cameraId, + Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface, OutputSurface imageCaptureSurface, OutputSurface postviewSurface) { OutputSurfaceImplStub outputPreviewSurfaceImpl = new OutputSurfaceImplStub(previewSurface); @@ -1211,10 +1221,12 @@ public class CameraExtensionsProxyService extends Service { outputPostviewSurfaceImpl); sessionConfig = mSessionProcessor.initSession(cameraId, - mCharacteristicsHashMap, getApplicationContext(), outputSurfaceConfigs); + getCharacteristicsMap(charsMapNative), + getApplicationContext(), outputSurfaceConfigs); } else { sessionConfig = mSessionProcessor.initSession(cameraId, - mCharacteristicsHashMap, getApplicationContext(), outputPreviewSurfaceImpl, + getCharacteristicsMap(charsMapNative), + getApplicationContext(), outputPreviewSurfaceImpl, outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/); } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index fbc7b3cbc63b..874fb0164b01 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -1072,6 +1072,10 @@ public class FullScreenMagnificationController implements } } + protected float getLastActivatedScale(int displayId) { + return getScale(displayId); + } + /** * Returns the X offset of the magnification viewport. If an animation * is in progress, this reflects the end state of the animation. 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 7ee72dfa30fd..9a519fac2d26 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -203,19 +203,27 @@ public class MagnificationController implements WindowMagnificationManager.Callb private void updateMagnificationUIControls(int displayId, int mode) { final boolean isActivated = isActivated(displayId, mode); - final boolean showUIControls; + final boolean showModeSwitchButton; + final boolean enableSettingsPanel; synchronized (mLock) { - showUIControls = isActivated && mMagnificationCapabilities - == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + showModeSwitchButton = isActivated + && mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL; + enableSettingsPanel = isActivated + && (mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_ALL + || mMagnificationCapabilities == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } - if (showUIControls) { - // we only need to show magnification button, the settings panel showing should be - // triggered only on sysui side. + + if (showModeSwitchButton) { getWindowMagnificationMgr().showMagnificationButton(displayId, mode); } else { - getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId); getWindowMagnificationMgr().removeMagnificationButton(displayId); } + + if (!enableSettingsPanel) { + // Whether the settings panel needs to be shown is controlled in system UI. + // Here, we only guarantee that the settings panel is closed when it is not needed. + getWindowMagnificationMgr().removeMagnificationSettingsPanel(displayId); + } } /** Returns {@code true} if the platform supports window magnification feature. */ @@ -470,12 +478,14 @@ public class MagnificationController implements WindowMagnificationManager.Callb disableFullScreenMagnificationIfNeeded(displayId); } else { long duration; + float scale; synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId); + scale = mWindowMagnificationMgr.getLastActivatedScale(displayId); } - logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration); + logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale); } updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } @@ -567,13 +577,16 @@ public class MagnificationController implements WindowMagnificationManager.Callb disableWindowMagnificationIfNeeded(displayId); } else { long duration; + float scale; synchronized (mLock) { setCurrentMagnificationModeAndSwitchDelegate(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_NONE); duration = SystemClock.uptimeMillis() - mFullScreenModeEnabledTimeArray.get(displayId); + scale = mFullScreenMagnificationController.getLastActivatedScale(displayId); } - logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration); + logMagnificationUsageState( + ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration, scale); } updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @@ -612,10 +625,11 @@ public class MagnificationController implements WindowMagnificationManager.Callb * * @param mode The activated magnification mode. * @param duration The duration in milliseconds during the magnification is activated. + * @param scale The last magnification scale for the activation */ @VisibleForTesting - public void logMagnificationUsageState(int mode, long duration) { - AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration); + public void logMagnificationUsageState(int mode, long duration, float scale) { + AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration, scale); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java index ce18b2cf9bad..d07db3f7578d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java @@ -142,6 +142,8 @@ public class WindowMagnificationManager implements private boolean mMagnificationFollowTypingEnabled = true; @GuardedBy("mLock") private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray(); + @GuardedBy("mLock") + private final SparseArray<Float> mLastActivatedScale = new SparseArray<>(); private boolean mReceiverRegistered = false; @VisibleForTesting @@ -528,6 +530,7 @@ public class WindowMagnificationManager implements return; } magnifier.setScale(scale); + mLastActivatedScale.put(displayId, scale); } } @@ -615,6 +618,9 @@ public class WindowMagnificationManager implements previousEnabled = magnifier.mEnabled; enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY, animationCallback, windowPosition, id); + if (enabled) { + mLastActivatedScale.put(displayId, getScale(displayId)); + } } if (enabled) { @@ -752,6 +758,15 @@ public class WindowMagnificationManager implements } } + protected float getLastActivatedScale(int displayId) { + synchronized (mLock) { + if (!mLastActivatedScale.contains(displayId)) { + return -1.0f; + } + return mLastActivatedScale.get(displayId); + } + } + /** * Moves window magnification on the specified display with the specified offset. * diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index fb94af65513f..311300043b1b 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1726,6 +1726,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } } + if (ids.isEmpty()) return saveInfo; AutofillId[] autofillIds = new AutofillId[ids.size()]; ids.toArray(autofillIds); return SaveInfo.copy(saveInfo, autofillIds); diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 3a7aa855ea90..7a3b1190cffa 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -813,7 +813,7 @@ public class BackupManagerService extends IBackupManager.Stub { } UserBackupManagerService userBackupManagerService = getServiceForUserIfCallerHasPermission( - UserHandle.USER_SYSTEM, "hasBackupPassword()"); + userId, "hasBackupPassword()"); return userBackupManagerService != null && userBackupManagerService.hasBackupPassword(); } @@ -1515,41 +1515,73 @@ public class BackupManagerService extends IBackupManager.Stub { @VisibleForTesting void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) { - int userId = binderGetCallingUserId(); - if (!isUserReadyForBackup(userId)) { - pw.println("Inactive"); + int argIndex = 0; + + String op = nextArg(args, argIndex); + argIndex++; + + if ("--help".equals(op)) { + showDumpUsage(pw); return; } - - if (args != null) { - for (String arg : args) { - if ("-h".equals(arg)) { - pw.println("'dumpsys backup' optional arguments:"); - pw.println(" -h : this help text"); - pw.println(" a[gents] : dump information about defined backup agents"); - pw.println(" transportclients : dump information about transport clients"); - pw.println(" transportstats : dump transport statts"); - pw.println(" users : dump the list of users for which backup service " - + "is running"); - return; - } else if ("users".equals(arg.toLowerCase())) { - pw.print(DUMP_RUNNING_USERS_MESSAGE); - for (int i = 0; i < mUserServices.size(); i++) { - pw.print(" " + mUserServices.keyAt(i)); - } - pw.println(); - return; + if ("users".equals(op)) { + pw.print(DUMP_RUNNING_USERS_MESSAGE); + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), + "dump()"); + if (userBackupManagerService != null) { + pw.print(" " + userBackupManagerService.getUserId()); } } + pw.println(); + return; } - - for (int i = 0; i < mUserServices.size(); i++) { + if ("--user".equals(op)) { + String userArg = nextArg(args, argIndex); + argIndex++; + if (userArg == null) { + showDumpUsage(pw); + return; + } + int userId = UserHandle.parseUserArg(userArg); UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + getServiceForUserIfCallerHasPermission(userId, "dump()"); if (userBackupManagerService != null) { userBackupManagerService.dump(fd, pw, args); } + return; } + if (op == null || "agents".startsWith(op) || "transportclients".equals(op) + || "transportstats".equals(op)) { + for (int i = 0; i < mUserServices.size(); i++) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()"); + if (userBackupManagerService != null) { + userBackupManagerService.dump(fd, pw, args); + } + } + return; + } + + showDumpUsage(pw); + } + + private String nextArg(String[] args, int argIndex) { + if (argIndex >= args.length) { + return null; + } + return args[argIndex]; + } + + private static void showDumpUsage(PrintWriter pw) { + pw.println("'dumpsys backup' optional arguments:"); + pw.println(" --help : this help text"); + pw.println(" a[gents] : dump information about defined backup agents"); + pw.println(" transportclients : dump information about transport clients"); + pw.println(" transportstats : dump transport stats"); + pw.println(" users : dump the list of users for which backup service is running"); + pw.println(" --user <userId> : dump information for user userId"); } /** @@ -1654,7 +1686,7 @@ public class BackupManagerService extends IBackupManager.Stub { * @param message A message to include in the exception if it is thrown. */ void enforceCallingPermissionOnUserId(@UserIdInt int userId, String message) { - if (Binder.getCallingUserHandle().getIdentifier() != userId) { + if (binderGetCallingUserId() != userId) { mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 7261709d7b8d..b65681104527 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -2797,11 +2797,6 @@ public class UserBackupManagerService { boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup"); - final int callingUserHandle = UserHandle.getCallingUserId(); - if (callingUserHandle != UserHandle.USER_SYSTEM) { - throw new IllegalStateException("Backup supported only for the device owner"); - } - // Validate if (!doAllApps) { if (!includeShared) { @@ -2972,11 +2967,6 @@ public class UserBackupManagerService { public void adbRestore(ParcelFileDescriptor fd) { mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbRestore"); - final int callingUserHandle = UserHandle.getCallingUserId(); - if (callingUserHandle != UserHandle.USER_SYSTEM) { - throw new IllegalStateException("Restore supported only for the device owner"); - } - final long oldId = Binder.clearCallingIdentity(); try { @@ -3085,7 +3075,7 @@ public class UserBackupManagerService { "com.android.backupconfirm.BackupRestoreConfirmation"); confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); confIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - mContext.startActivityAsUser(confIntent, UserHandle.SYSTEM); + mContext.startActivityAsUser(confIntent, UserHandle.of(mUserId)); } catch (ActivityNotFoundException e) { return false; } diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index e9cd84aaf0be..77275e056db6 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -288,7 +288,7 @@ class AssociationRequestsProcessor { final AssociationInfo association = new AssociationInfo(id, userId, packageName, macAddress, displayName, deviceProfile, associatedDevice, selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE, - /* systemDataSyncFlags */ ~0); + /* systemDataSyncFlags */ 0); if (deviceProfile != null) { // If the "Device Profile" is specified, make the companion application a holder of the diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java index 05f2eea621cf..8570515f241d 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java @@ -31,10 +31,10 @@ public class CompanionDeviceConfig { public static final String ENABLE_CONTEXT_SYNC_TELECOM = "enable_context_sync_telecom"; /** - * Returns whether the given flag is currently enabled, with a default value of {@code true}. + * 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= */ true); + return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false); } /** diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java index b66c1937b8dd..54798f4f7fcc 100644 --- a/services/companion/java/com/android/server/companion/PersistentDataStore.java +++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java @@ -133,7 +133,7 @@ import java.util.concurrent.ConcurrentMap; * revoked="false" * last_time_connected="1634641160229" * time_approved="1634389553216" - * system_data_sync_flags="-1"/> + * system_data_sync_flags="0"/> * * <association * id="3" @@ -145,7 +145,7 @@ import java.util.concurrent.ConcurrentMap; * revoked="false" * last_time_connected="1634641160229" * time_approved="1634641160229" - * system_data_sync_flags="-1"/> + * system_data_sync_flags="1"/> * </associations> * * <previously-used-ids> @@ -432,7 +432,7 @@ final class PersistentDataStore { out.add(new AssociationInfo(associationId, userId, appPackage, MacAddress.fromString(deviceAddress), null, profile, null, /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved, - Long.MAX_VALUE, /* systemDataSyncFlags */ -1)); + Long.MAX_VALUE, /* systemDataSyncFlags */ 0)); } private static void readAssociationsV1(@NonNull TypedXmlPullParser parser, @@ -466,7 +466,7 @@ final class PersistentDataStore { final long lastTimeConnected = readLongAttribute( parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE); final int systemDataSyncFlags = readIntAttribute(parser, - XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, -1); + XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0); final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId, appPackage, macAddress, displayName, profile, selfManaged, notify, revoked, diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index 17bdb6085c45..0f00f5f1c3a5 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -19,10 +19,8 @@ package com.android.server.companion.transport; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE; -import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManagerInternal; import android.companion.AssociationInfo; @@ -33,7 +31,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Binder; import android.os.Build; -import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -46,7 +43,6 @@ import com.android.server.companion.AssociationStore; import java.io.FileDescriptor; import java.io.IOException; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -58,19 +54,8 @@ public class CompanionTransportManager { private static final String TAG = "CDM_CompanionTransportManager"; private static final boolean DEBUG = false; - private static final int SECURE_CHANNEL_AVAILABLE_SDK = Build.VERSION_CODES.UPSIDE_DOWN_CAKE; - private static final int NON_ANDROID = -1; - private boolean mSecureTransportEnabled = true; - private static boolean isRequest(int message) { - return (message & 0xFF000000) == 0x63000000; - } - - private static boolean isResponse(int message) { - return (message & 0xFF000000) == 0x33000000; - } - private final Context mContext; private final AssociationStore mAssociationStore; @@ -84,10 +69,6 @@ public class CompanionTransportManager { @NonNull private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>(); - - @Nullable - private Transport mTempTransport; - public CompanionTransportManager(Context context, AssociationStore associationStore) { mContext = context; mAssociationStore = associationStore; @@ -199,7 +180,8 @@ public class CompanionTransportManager { detachSystemDataTransport(packageName, userId, associationId); } - initializeTransport(associationId, fd); + // TODO: Implement new API to pass a PSK + initializeTransport(associationId, fd, null); notifyOnTransportsChanged(); } @@ -237,107 +219,36 @@ public class CompanionTransportManager { }); } - private void initializeTransport(int associationId, ParcelFileDescriptor fd) { + private void initializeTransport(int associationId, + ParcelFileDescriptor fd, + byte[] preSharedKey) { Slog.i(TAG, "Initializing transport"); + Transport transport; if (!isSecureTransportEnabled()) { - Transport transport = new RawTransport(associationId, fd, mContext); - addMessageListenersToTransport(transport); - transport.start(); - synchronized (mTransports) { - mTransports.put(associationId, transport); - } - Slog.i(TAG, "RawTransport is created"); - return; - } - - // Exchange platform info to decide which transport should be created - mTempTransport = new RawTransport(associationId, fd, mContext); - addMessageListenersToTransport(mTempTransport); - IOnMessageReceivedListener listener = new IOnMessageReceivedListener() { - @Override - public void onMessageReceived(int associationId, byte[] data) throws RemoteException { - synchronized (mTransports) { - onPlatformInfoReceived(associationId, data); - } - } - - @Override - public IBinder asBinder() { - return null; - } - }; - mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, listener); - mTempTransport.start(); - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - // data format: | SDK_INT (int) | release length (int) | release | - final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length) - .putInt(sdk) - .putInt(release.getBytes().length) - .put(release.getBytes()); - - // TODO: it should check if preSharedKey is given - try { - mTempTransport.sendMessage(MESSAGE_REQUEST_PLATFORM_INFO, data.array()); - } catch (IOException e) { - Slog.e(TAG, "Failed to exchange platform info"); - } - } - - /** - * Depending on the remote platform info to decide which transport should be created - */ - private void onPlatformInfoReceived(int associationId, byte[] data) { - if (mTempTransport.getAssociationId() != associationId) { - return; - } - // TODO: it should check if preSharedKey is given - - ByteBuffer buffer = ByteBuffer.wrap(data); - int remoteSdk = buffer.getInt(); - byte[] remoteRelease = new byte[buffer.getInt()]; - buffer.get(remoteRelease); - - Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease)); - - Transport transport = mTempTransport; - mTempTransport.stop(); - - int sdk = Build.VERSION.SDK_INT; - String release = Build.VERSION.RELEASE; - - if (sdk < SECURE_CHANNEL_AVAILABLE_SDK || remoteSdk < SECURE_CHANNEL_AVAILABLE_SDK) { - // If either device is Android T or below, use raw channel - // TODO: depending on the release version, either - // 1) using a RawTransport for old T versions - // 2) or an Ukey2 handshaked transport for UKey2 backported T versions - Slog.d(TAG, "Secure channel is not supported. Using raw transport"); - transport = new RawTransport(transport.getAssociationId(), transport.getFd(), mContext); + // If secure transport is explicitly disabled for testing, use raw transport + Slog.i(TAG, "Secure channel is disabled. Creating raw transport"); + transport = new RawTransport(associationId, fd, mContext); } else if (Build.isDebuggable()) { // If device is debug build, use hardcoded test key for authentication Slog.d(TAG, "Creating an unauthenticated secure channel"); final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext, testKey, null); - } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) { + transport = new SecureTransport(associationId, fd, mContext, testKey, null); + } else if (preSharedKey != null) { // If either device is not Android, then use app-specific pre-shared key - // TODO: pass in a real preSharedKey Slog.d(TAG, "Creating a PSK-authenticated secure channel"); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext, new byte[0], null); + transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null); } else { // If none of the above applies, then use secure channel with attestation verification Slog.d(TAG, "Creating a secure channel"); - transport = new SecureTransport(transport.getAssociationId(), transport.getFd(), - mContext); + transport = new SecureTransport(associationId, fd, mContext); } + addMessageListenersToTransport(transport); transport.start(); synchronized (mTransports) { - mTransports.put(transport.getAssociationId(), transport); + mTransports.put(associationId, transport); } - // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport + } public Future<?> requestPermissionRestore(int associationId, byte[] data) { diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java index 41589018b149..e64509facbb4 100644 --- a/services/companion/java/com/android/server/companion/transport/RawTransport.java +++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java @@ -35,7 +35,7 @@ class RawTransport extends Transport { } @Override - public void start() { + void start() { if (DEBUG) { Slog.d(TAG, "Starting raw transport."); } @@ -54,7 +54,7 @@ class RawTransport extends Transport { } @Override - public void stop() { + void stop() { if (DEBUG) { Slog.d(TAG, "Stopping raw transport."); } @@ -62,7 +62,7 @@ class RawTransport extends Transport { } @Override - public void close() { + void close() { stop(); if (DEBUG) { 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 4054fc95f04a..949f39ae1609 100644 --- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java +++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java @@ -51,18 +51,18 @@ class SecureTransport extends Transport implements SecureChannel.Callback { } @Override - public void start() { + void start() { mSecureChannel.start(); } @Override - public void stop() { + void stop() { mSecureChannel.stop(); mShouldProcessRequests = false; } @Override - public void close() { + void close() { mSecureChannel.close(); mShouldProcessRequests = false; } diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java index d30104a095cf..6ad6d3a4aa72 100644 --- a/services/companion/java/com/android/server/companion/transport/Transport.java +++ b/services/companion/java/com/android/server/companion/transport/Transport.java @@ -47,7 +47,6 @@ public abstract class Transport { protected static final boolean DEBUG = Build.IS_DEBUGGABLE; static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN - public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807073; // ?PFI public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES @@ -113,17 +112,17 @@ public abstract class Transport { /** * Start listening to messages. */ - public abstract void start(); + abstract void start(); /** * Soft stop listening to the incoming data without closing the streams. */ - public abstract void stop(); + abstract void stop(); /** * Stop listening to the incoming data and close the streams. */ - public abstract void close(); + abstract void close(); protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data) throws IOException; @@ -183,11 +182,6 @@ public abstract class Transport { sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data); break; } - case MESSAGE_REQUEST_PLATFORM_INFO: { - callback(message, data); - // DO NOT SEND A RESPONSE! - break; - } case MESSAGE_REQUEST_CONTEXT_SYNC: { callback(message, data); sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE); diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java index 99178920cc52..2812233815a6 100644 --- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java +++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java @@ -88,7 +88,7 @@ public class WallpaperUpdateReceiver extends BroadcastReceiver { } else { //live wallpaper ComponentName currCN = info.getComponent(); - ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context); + ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context); if (!currCN.equals(defaultCN)) { return true; } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index ca50af8075c6..96766a20c803 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2326,7 +2326,7 @@ public final class ActiveServices { && (r.getConnections().size() > 0) && (r.mDebugWhileInUseReasonInBindService != r.mDebugWhileInUseReasonInStartForeground)) { - Slog.wtf(TAG, "FGS while-in-use changed (b/276963716): old=" + logWhileInUseChangeWtf("FGS while-in-use changed (b/276963716): old=" + reasonCodeToString(r.mDebugWhileInUseReasonInBindService) + " new=" + reasonCodeToString(r.mDebugWhileInUseReasonInStartForeground) @@ -2589,6 +2589,13 @@ public final class ActiveServices { } } + /** + * It just does a wtf, but extracted to a method, so we can do a signature search on pitot. + */ + private void logWhileInUseChangeWtf(String message) { + Slog.wtf(TAG, message); + } + private boolean withinFgsDeferRateLimit(ServiceRecord sr, final long now) { // If we're still within the service's deferral period, then by definition // deferral is not rate limited. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5d3bb31bc31f..a4cd2780bec6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3761,6 +3761,15 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void forceStopPackage(final String packageName, int userId) { + forceStopPackage(packageName, userId, /*flags=*/ 0); + } + + @Override + public void forceStopPackageEvenWhenStopping(final String packageName, int userId) { + forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED); + } + + private void forceStopPackage(final String packageName, int userId, int userRunningFlags) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: forceStopPackage() from pid=" @@ -3776,7 +3785,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long callingId = Binder.clearCallingIdentity(); try { IPackageManager pm = AppGlobals.getPackageManager(); - synchronized(this) { + synchronized (this) { int[] users = userId == UserHandle.USER_ALL ? mUserController.getUsers() : new int[] { userId }; for (int user : users) { @@ -3804,7 +3813,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Failed trying to unstop package " + packageName + ": " + e); } - if (mUserController.isUserRunning(user, 0)) { + if (mUserController.isUserRunning(user, userRunningFlags)) { forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid); finishForceStopPackageLocked(packageName, pkgUid); } @@ -18964,6 +18973,13 @@ public class ActivityManagerService extends IActivityManager.Stub pw.flush(); } + void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) { + enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch"); + for (BroadcastQueue queue : mBroadcastQueues) { + queue.waitForDispatched(intent, pw); + } + } + void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) { Objects.requireNonNull(broadcastAction); enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()"); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 17a0d62c27b3..8759e3f207c4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -368,6 +368,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runWaitForBroadcastBarrier(pw); case "wait-for-application-barrier": return runWaitForApplicationBarrier(pw); + case "wait-for-broadcast-dispatch": + return runWaitForBroadcastDispatch(pw); case "set-ignore-delivery-group-policy": return runSetIgnoreDeliveryGroupPolicy(pw); case "clear-ignore-delivery-group-policy": @@ -3472,6 +3474,18 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException { + pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw)); + final Intent intent; + try { + intent = makeIntent(UserHandle.USER_CURRENT); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage(), e); + } + mInternal.waitForBroadcastDispatch(pw, intent); + return 0; + } + int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException { final String broadcastAction = getNextArgRequired(); mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction); diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 4d469639be6f..87214decfe2e 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -179,11 +179,39 @@ public class BroadcastConstants { * being "runnable" to give other processes a chance to run. */ public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS; - private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts"; + private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = + "bcast_max_running_active_broadcasts"; private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = ActivityManager.isLowRamDeviceStatic() ? 8 : 16; /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active "blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_BLOCKING_BROADCASTS = DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS = + "bcast_max_core_running_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 8 : 16; + + /** + * For {@link BroadcastQueueModernImpl}: Maximum number of active non-"blocking" broadcasts + * to dispatch to a "running" System process queue before we retire them back to + * being "runnable" to give other processes a chance to run. Here "blocking" refers to + * whether or not we are going to block on the finishReceiver() to be called before moving + * to the next broadcast. + */ + public int MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS; + private static final String KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS = + "bcast_max_core_running_non_blocking_broadcasts"; + private static final int DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = + ActivityManager.isLowRamDeviceStatic() ? 32 : 64; + + /** * For {@link BroadcastQueueModernImpl}: Maximum number of pending * broadcasts to hold for a process before we ignore any delays that policy * might have applied to that process. @@ -369,6 +397,12 @@ public class BroadcastConstants { DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES); MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS); + MAX_CORE_RUNNING_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS); + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = getDeviceConfigInt( + KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS, DEFAULT_MAX_PENDING_BROADCASTS); DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS, @@ -418,6 +452,10 @@ public class BroadcastConstants { pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println(); pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println(); pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_BLOCKING_BROADCASTS).println(); + pw.print(KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS, + MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS).println(); pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println(); pw.print(KEY_DELAY_NORMAL_MILLIS, TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println(); diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java index 2adcf2f48343..8aa3921d3f2f 100644 --- a/services/core/java/com/android/server/am/BroadcastDispatcher.java +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -582,6 +582,38 @@ public class BroadcastDispatcher { } } + private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (!isDispatched(list.get(i).broadcasts, intent)) { + return false; + } + } + return true; + } + + private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list, + @NonNull Intent intent) { + for (int i = 0; i < list.size(); i++) { + if (intent.filterEquals(list.get(i).intent)) { + return false; + } + } + return true; + } + + public boolean isDispatched(@NonNull Intent intent) { + synchronized (mLock) { + if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) { + return false; + } + return isDispatched(mOrderedBroadcasts, intent) + && isDispatched(mAlarmQueue, intent) + && isDispatchedInDeferrals(mDeferredBroadcasts, intent) + && isDispatchedInDeferrals(mAlarmDeferrals, intent); + } + } + private static int pendingInDeferralsList(ArrayList<Deferrals> list) { int pending = 0; final int numEntries = list.size(); diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 5c68e6759083..2803b4b66615 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -143,6 +143,12 @@ class BroadcastProcessQueue { private int mActiveCountSinceIdle; /** + * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched + * since this queue was last idle. + */ + private int mActiveAssumedDeliveryCountSinceIdle; + + /** * Flag indicating that the currently active broadcast is being dispatched * was scheduled via a cold start. */ @@ -182,7 +188,7 @@ class BroadcastProcessQueue { private int mCountInstrumented; private int mCountManifest; - private boolean mPrioritizeEarliest; + private int mCountPrioritizeEarliestRequests; private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE; private @Reason int mRunnableAtReason = REASON_EMPTY; @@ -194,6 +200,7 @@ class BroadcastProcessQueue { */ private boolean mLastDeferredStates; + private boolean mUidForeground; private boolean mUidCached; private boolean mProcessInstrumented; private boolean mProcessPersistent; @@ -403,7 +410,8 @@ class BroadcastProcessQueue { * {@link BroadcastQueueModernImpl#updateRunnableList} */ @CheckResult - public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { + public boolean setProcessAndUidState(@Nullable ProcessRecord app, boolean uidForeground, + boolean uidCached) { this.app = app; // Since we may have just changed our PID, invalidate cached strings @@ -413,10 +421,12 @@ class BroadcastProcessQueue { boolean didSomething = false; if (app != null) { didSomething |= setUidCached(uidCached); + didSomething |= setUidForeground(uidForeground); didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null); didSomething |= setProcessPersistent(app.isPersistent()); } else { didSomething |= setUidCached(uidCached); + didSomething |= setUidForeground(false); didSomething |= setProcessInstrumented(false); didSomething |= setProcessPersistent(false); } @@ -424,6 +434,22 @@ class BroadcastProcessQueue { } /** + * Update if the UID this process is belongs to is in "foreground" state, which signals + * broadcast dispatch should prioritize delivering broadcasts to this process to minimize any + * delays in UI updates. + */ + @CheckResult + private boolean setUidForeground(boolean uidForeground) { + if (mUidForeground != uidForeground) { + mUidForeground = uidForeground; + invalidateRunnableAt(); + return true; + } else { + return false; + } + } + + /** * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ @@ -499,6 +525,14 @@ class BroadcastProcessQueue { return mActiveCountSinceIdle; } + /** + * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched + * since this queue was last idle. + */ + public int getActiveAssumedDeliveryCountSinceIdle() { + return mActiveAssumedDeliveryCountSinceIdle; + } + public void setActiveViaColdStart(boolean activeViaColdStart) { mActiveViaColdStart = activeViaColdStart; } @@ -532,6 +566,8 @@ class BroadcastProcessQueue { mActive = (BroadcastRecord) next.arg1; mActiveIndex = next.argi1; mActiveCountSinceIdle++; + mActiveAssumedDeliveryCountSinceIdle += + (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0); mActiveViaColdStart = false; mActiveWasStopped = false; next.recycle(); @@ -545,6 +581,7 @@ class BroadcastProcessQueue { mActive = null; mActiveIndex = 0; mActiveCountSinceIdle = 0; + mActiveAssumedDeliveryCountSinceIdle = 0; mActiveViaColdStart = false; invalidateRunnableAt(); } @@ -748,7 +785,7 @@ class BroadcastProcessQueue { final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1; final int nextLPRecordIndex = nextLPArgs.argi1; final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1; - final boolean shouldConsiderLPQueue = (mPrioritizeEarliest + final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime @@ -761,10 +798,9 @@ class BroadcastProcessQueue { } /** - * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued - * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts - * waiting. This is typically used in case there are callers waiting for "barrier" to be - * reached. + * Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest, + * even if there are urgent broadcasts waiting to be dispatched. This is typically used in + * case there are callers waiting for "barrier" to be reached. * * @return if this operation may have changed internal state, indicating * that the caller is responsible for invoking @@ -772,12 +808,38 @@ class BroadcastProcessQueue { */ @CheckResult @VisibleForTesting - boolean setPrioritizeEarliest(boolean prioritizeEarliest) { - if (mPrioritizeEarliest != prioritizeEarliest) { - mPrioritizeEarliest = prioritizeEarliest; + boolean addPrioritizeEarliestRequest() { + if (mCountPrioritizeEarliestRequests == 0) { + mCountPrioritizeEarliestRequests++; invalidateRunnableAt(); return true; } else { + mCountPrioritizeEarliestRequests++; + return false; + } + } + + /** + * Remove a request to prioritize dispatching of broadcasts that have been enqueued the + * earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically + * used in case there are callers waiting for "barrier" to be reached. + * + * <p> Once there are no more remaining requests, the dispatching order reverts back to normal. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} + */ + @CheckResult + boolean removePrioritizeEarliestRequest() { + mCountPrioritizeEarliestRequests--; + if (mCountPrioritizeEarliestRequests == 0) { + invalidateRunnableAt(); + return true; + } else if (mCountPrioritizeEarliestRequests < 0) { + mCountPrioritizeEarliestRequests = 0; + return false; + } else { return false; } } @@ -837,7 +899,7 @@ class BroadcastProcessQueue { } /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. */ public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) { @@ -859,6 +921,41 @@ class BroadcastProcessQueue { || isDeferredUntilActive(); } + /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + public boolean isDispatched(@NonNull Intent intent) { + final boolean activeDispatched = (mActive == null) + || (!intent.filterEquals(mActive.intent)); + final boolean dispatched = isDispatchedInQueue(mPending, intent); + final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent); + final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent); + + return (activeDispatched && dispatched && urgentDispatched && offloadDispatched) + || isDeferredUntilActive(); + } + + /** + * Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + */ + private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue, + @NonNull Intent intent) { + final Iterator<SomeArgs> it = queue.iterator(); + while (it.hasNext()) { + final SomeArgs args = it.next(); + if (args == null) { + return true; + } + final BroadcastRecord record = (BroadcastRecord) args.arg1; + if (intent.filterEquals(record.intent)) { + return false; + } + } + return true; + } + public boolean isRunnable() { if (mRunnableAtInvalidated) updateRunnableAt(); return mRunnableAt != Long.MAX_VALUE; @@ -917,7 +1014,7 @@ class BroadcastProcessQueue { static final int REASON_CONTAINS_RESULT_TO = 15; static final int REASON_CONTAINS_INSTRUMENTED = 16; static final int REASON_CONTAINS_MANIFEST = 17; - static final int REASON_FOREGROUND_ACTIVITIES = 18; + static final int REASON_FOREGROUND = 18; @IntDef(flag = false, prefix = { "REASON_" }, value = { REASON_EMPTY, @@ -937,7 +1034,7 @@ class BroadcastProcessQueue { REASON_CONTAINS_RESULT_TO, REASON_CONTAINS_INSTRUMENTED, REASON_CONTAINS_MANIFEST, - REASON_FOREGROUND_ACTIVITIES, + REASON_FOREGROUND, }) @Retention(RetentionPolicy.SOURCE) public @interface Reason {} @@ -961,7 +1058,7 @@ class BroadcastProcessQueue { case REASON_CONTAINS_RESULT_TO: return "CONTAINS_RESULT_TO"; case REASON_CONTAINS_INSTRUMENTED: return "CONTAINS_INSTRUMENTED"; case REASON_CONTAINS_MANIFEST: return "CONTAINS_MANIFEST"; - case REASON_FOREGROUND_ACTIVITIES: return "FOREGROUND_ACTIVITIES"; + case REASON_FOREGROUND: return "FOREGROUND"; default: return Integer.toString(reason); } } @@ -1000,11 +1097,9 @@ class BroadcastProcessQueue { } else if (mProcessInstrumented) { mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; mRunnableAtReason = REASON_INSTRUMENTED; - } else if (app != null && app.hasForegroundActivities()) { - // TODO: Listen for uid state changes to check when an uid goes in and out of - // the TOP state. + } else if (mUidForeground) { mRunnableAt = runnableAt + constants.DELAY_URGENT_MILLIS; - mRunnableAtReason = REASON_FOREGROUND_ACTIVITIES; + mRunnableAtReason = REASON_FOREGROUND; } else if (mCountOrdered > 0) { mRunnableAt = runnableAt; mRunnableAtReason = REASON_CONTAINS_ORDERED; @@ -1274,7 +1369,11 @@ class BroadcastProcessQueue { @NeverCompile private void dumpProcessState(@NonNull IndentingPrintWriter pw) { final StringBuilder sb = new StringBuilder(); + if (mUidForeground) { + sb.append("FG"); + } if (mUidCached) { + if (sb.length() > 0) sb.append("|"); sb.append("CACHED"); } if (mProcessInstrumented) { @@ -1309,6 +1408,7 @@ class BroadcastProcessQueue { pw.print(" m:"); pw.print(mCountManifest); pw.print(" csi:"); pw.print(mActiveCountSinceIdle); + pw.print(" adcsi:"); pw.print(mActiveAssumedDeliveryCountSinceIdle); pw.print(" ccu:"); pw.print(mActiveCountConsecutiveUrgent); pw.print(" ccn:"); pw.print(mActiveCountConsecutiveNormal); pw.println(); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 6d1344d79b6c..8e76e5b5cf48 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -192,7 +192,7 @@ public abstract class BroadcastQueue { public abstract boolean isIdleLocked(); /** - * Quickly determine if this queue has broadcasts enqueued before the given + * Quickly determine if this queue has non-deferred broadcasts enqueued before the given * barrier timestamp that are still waiting to be delivered. * * @see #waitForIdle @@ -202,6 +202,15 @@ public abstract class BroadcastQueue { public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime); /** + * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched, + * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}. + * + * @see #waitForDispatched(Intent, PrintWriter) + */ + @GuardedBy("mService") + public abstract boolean isDispatchedLocked(@NonNull Intent intent); + + /** * Wait until this queue becomes completely idle. * <p> * Any broadcasts waiting to be delivered at some point in the future will @@ -214,7 +223,7 @@ public abstract class BroadcastQueue { public abstract void waitForIdle(@NonNull PrintWriter pw); /** - * Wait until any currently waiting broadcasts have been dispatched. + * Wait until any currently waiting non-deferred broadcasts have been dispatched. * <p> * Any broadcasts waiting to be delivered at some point in the future will * be dispatched as quickly as possible. @@ -225,6 +234,15 @@ public abstract class BroadcastQueue { public abstract void waitForBarrier(@NonNull PrintWriter pw); /** + * Wait until all non-deferred broadcasts matching {@code intent}, as defined by + * {@link Intent#filterEquals(Intent)}, have been dispatched. + * <p> + * Any broadcasts waiting to be delivered at some point in the future will + * be dispatched as quickly as possible. + */ + public abstract void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw); + + /** * Delays delivering broadcasts to the specified package. * * <p> Note that this is only valid for modern queue. diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index 4a69f90d9fc0..7f3ceb578891 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1793,6 +1793,23 @@ public class BroadcastQueueImpl extends BroadcastQueue { return mDispatcher.isBeyondBarrier(barrierTime); } + public boolean isDispatchedLocked(Intent intent) { + if (isIdleLocked()) return true; + + for (int i = 0; i < mParallelBroadcasts.size(); i++) { + if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) { + return false; + } + } + + final BroadcastRecord pending = getPendingBroadcastLocked(); + if ((pending != null) && intent.filterEquals(pending.intent)) { + return false; + } + + return mDispatcher.isDispatched(intent); + } + public void waitForIdle(PrintWriter pw) { waitFor(() -> isIdleLocked(), pw, "idle"); } @@ -1802,6 +1819,10 @@ public class BroadcastQueueImpl extends BroadcastQueue { waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier"); } + public void waitForDispatched(Intent intent, PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent), pw, "dispatch"); + } + private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) { long lastPrint = 0; while (true) { diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 96e152320282..d9b315794ea3 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -40,6 +40,7 @@ import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UptimeMillisLong; @@ -211,6 +212,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { new AtomicReference<>(); /** + * Map from UID to its last known "foreground" state. A UID is considered to be in + * "foreground" state when it's procState is {@link ActivityManager#PROCESS_STATE_TOP}. + * <p> + * We manually maintain this data structure since the lifecycle of + * {@link ProcessRecord} and {@link BroadcastProcessQueue} can be + * mismatched. + */ + @GuardedBy("mService") + private final SparseBooleanArray mUidForeground = new SparseBooleanArray(); + + /** * Map from UID to its last known "cached" state. * <p> * We manually maintain this data structure since the lifecycle of @@ -446,43 +458,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (DEBUG_BROADCAST) logv("Promoting " + queue + " from runnable to running; process is " + queue.app); - - // Allocate this available permit and start running! - final int queueIndex = getRunningIndexOf(null); - mRunning[queueIndex] = queue; - avail--; - - // Remove ourselves from linked list of runnable things - mRunnableHead = removeFromRunnableList(mRunnableHead, queue); - - // Emit all trace events for this process into a consistent track - queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; - queue.runningOomAdjusted = queue.isPendingManifest() - || queue.isPendingOrdered() - || queue.isPendingResultTo(); - - // If already warm, we can make OOM adjust request immediately; - // otherwise we need to wait until process becomes warm + promoteToRunningLocked(queue); + final boolean completed; if (processWarm) { - notifyStartedRunning(queue); updateOomAdj |= queue.runningOomAdjusted; - } - - // If we're already warm, schedule next pending broadcast now; - // otherwise we'll wait for the cold start to circle back around - queue.makeActiveNextPending(); - if (processWarm) { - queue.traceProcessRunningBegin(); - scheduleReceiverWarmLocked(queue); + completed = scheduleReceiverWarmLocked(queue); } else { - queue.traceProcessStartingBegin(); - scheduleReceiverColdLocked(queue); + completed = scheduleReceiverColdLocked(queue); + } + // If we are done with delivering the broadcasts to the process, we can demote it + // from the "running" list. + if (completed) { + demoteFromRunningLocked(queue); } + // TODO: If delivering broadcasts to a process is finished, we don't have to hold + // a slot for it. + avail--; // Move to considering next runnable queue queue = nextQueue; } + // TODO: We need to update oomAdj early as this currently doesn't guarantee that the + // procState is updated correctly when the app is handling a broadcast. if (updateOomAdj) { mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); } @@ -514,7 +512,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - scheduleReceiverWarmLocked(queue); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + } // We might be willing to kick off another cold start enqueueUpdateRunningList(); @@ -558,6 +558,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (queue.isActive()) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "onApplicationCleanupLocked"); + demoteFromRunningLocked(queue); } // Skip any pending registered receivers, since the old process @@ -695,8 +696,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * Schedule the currently active broadcast on the given queue when we know * the process is cold. This kicks off a cold start and will eventually call * through to {@link #scheduleReceiverWarmLocked} once it's ready. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ - private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { + @CheckResult + @GuardedBy("mService") + private boolean scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); // Remember that active broadcast was scheduled via a cold start @@ -711,12 +717,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, "BroadcastFilter for cold app"); - return; + return true; } - if (maybeSkipReceiver(queue, r, index)) { + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason != null) { mRunningColdStart = null; - return; + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + return true; } final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; @@ -742,8 +750,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "startProcessLocked failed"); - return; + return true; } + return false; } /** @@ -754,38 +763,46 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * results by calling through to {@link #finishReceiverLocked}, both in the * case where a broadcast is handled by a remote app, and the case where the * broadcast was finished locally without the remote app being involved. + * + * @return {@code true} if the broadcast delivery is finished and the process queue can + * be demoted from the running list. Otherwise {@code false}. */ + @CheckResult @GuardedBy("mService") - private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { + private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) { checkState(queue.isActive(), "isActive"); - final BroadcastRecord r = queue.getActive(); - final int index = queue.getActiveIndex(); + final int cookie = traceBegin("scheduleReceiverWarmLocked"); + while (queue.isActive()) { + final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); - if (r.terminalCount == 0) { - r.dispatchTime = SystemClock.uptimeMillis(); - r.dispatchRealTime = SystemClock.elapsedRealtime(); - r.dispatchClockTime = System.currentTimeMillis(); - } + if (r.terminalCount == 0) { + r.dispatchTime = SystemClock.uptimeMillis(); + r.dispatchRealTime = SystemClock.elapsedRealtime(); + r.dispatchClockTime = System.currentTimeMillis(); + } - if (maybeSkipReceiver(queue, r, index)) { - return; - } - dispatchReceivers(queue, r, index); - } + final String skipReason = shouldSkipReceiver(queue, r, index); + if (skipReason == null) { + final boolean isBlockingDispatch = dispatchReceivers(queue, r, index); + if (isBlockingDispatch) { + traceEnd(cookie); + return false; + } + } else { + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason); + } - /** - * Examine a receiver and possibly skip it. The method returns true if the receiver is - * skipped (and therefore no more work is required). - */ - private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index) { - final String reason = shouldSkipReceiver(queue, r, index); - if (reason != null) { - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, reason); - return true; + if (shouldRetire(queue)) { + break; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); } - return false; + traceEnd(cookie); + return true; } /** @@ -826,24 +843,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } /** - * Return true if this receiver should be assumed to have been delivered. - */ - private boolean isAssumedDelivered(BroadcastRecord r, int index) { - return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered - && (r.resultTo == null); - } - - /** * A receiver is about to be dispatched. Start ANR timers, if necessary. + * + * @return {@code true} if this a blocking delivery. That is, we are going to block on the + * finishReceiver() to be called before moving to the next broadcast. Otherwise, + * {@code false}. */ - private void dispatchReceivers(@NonNull BroadcastProcessQueue queue, + @CheckResult + private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue, @NonNull BroadcastRecord r, int index) { final ProcessRecord app = queue.app; final Object receiver = r.receivers.get(index); // Skip ANR tracking early during boot, when requested, or when we // immediately assume delivery success - final boolean assumeDelivered = isAssumedDelivered(r, index); + final boolean assumeDelivered = r.isAssumedDelivered(index); if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) { queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); @@ -898,6 +912,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (assumeDelivered) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered"); + return false; } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); @@ -908,17 +923,21 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.shareIdentity ? r.callingUid : Process.INVALID_UID, r.shareIdentity ? r.callerPackage : null); } + return true; } catch (RemoteException e) { final String msg = "Failed to schedule " + r + " to " + receiver + " via " + app + ": " + e; logw(msg); app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER, ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true); - finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, + "remote app"); + return false; } } else { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "missing IApplicationThread"); + return false; } } @@ -989,6 +1008,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, "deliveryTimeoutHardLocked"); + demoteFromRunningLocked(queue); } @Override @@ -1015,7 +1035,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // To ensure that "beyond" high-water marks are updated in a monotonic // way, we finish this receiver before possibly skipping any remaining // aborted receivers - final boolean res = finishReceiverActiveLocked(queue, + finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); // When the caller aborted an ordered broadcast, we mark all @@ -1027,30 +1047,52 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - return res; + if (shouldRetire(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + // We're on a roll; move onto the next broadcast for this process + queue.makeActiveNextPending(); + if (scheduleReceiverWarmLocked(queue)) { + demoteFromRunningLocked(queue); + return true; + } + + return false; } /** - * Return true if there are more broadcasts in the queue and the queue is runnable. + * Return true if there are no more broadcasts in the queue or if the queue is not runnable. */ - private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) { + private boolean shouldRetire(@NonNull BroadcastProcessQueue queue) { // If we've made reasonable progress, periodically retire ourselves to // avoid starvation of other processes and stack overflow when a // broadcast is immediately finished without waiting - final boolean shouldRetire = - (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + final boolean shouldRetire; + if (UserHandle.isCore(queue.uid)) { + final int nonBlockingDeliveryCount = queue.getActiveAssumedDeliveryCountSinceIdle(); + final int blockingDeliveryCount = (queue.getActiveCountSinceIdle() + - queue.getActiveAssumedDeliveryCountSinceIdle()); + shouldRetire = (blockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_BLOCKING_BROADCASTS) || (nonBlockingDeliveryCount + >= mConstants.MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS); + } else { + shouldRetire = + (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + } - return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire; + return !queue.isRunnable() || !queue.isProcessWarm() || shouldRetire; } /** * Terminate all active broadcasts on the queue. */ - private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, + private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue, @DeliveryState int deliveryState, @NonNull String reason) { if (!queue.isActive()) { logw("Ignoring finish; no active broadcast for " + queue); - return false; + return; } final int cookie = traceBegin("finishReceiver"); @@ -1077,27 +1119,68 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Given that a receiver just finished, check if the "waitingFor" conditions are met. checkAndRemoveWaitingFor(); - final boolean res = shouldContinueScheduling(queue); - if (res) { - // We're on a roll; move onto the next broadcast for this process - queue.makeActiveNextPending(); - scheduleReceiverWarmLocked(queue); - } else { - // We've drained running broadcasts; maybe move back to runnable - queue.makeActiveIdle(); - queue.traceProcessEnd(); + traceEnd(cookie); + } - final int queueIndex = getRunningIndexOf(queue); - mRunning[queueIndex] = null; - updateRunnableList(queue); - enqueueUpdateRunningList(); + /** + * Promote a process to the "running" list. + */ + @GuardedBy("mService") + private void promoteToRunningLocked(@NonNull BroadcastProcessQueue queue) { + // Allocate this available permit and start running! + final int queueIndex = getRunningIndexOf(null); + mRunning[queueIndex] = queue; + + // Remove ourselves from linked list of runnable things + mRunnableHead = removeFromRunnableList(mRunnableHead, queue); + + // Emit all trace events for this process into a consistent track + queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; + queue.runningOomAdjusted = queue.isPendingManifest() + || queue.isPendingOrdered() + || queue.isPendingResultTo(); + + // If already warm, we can make OOM adjust request immediately; + // otherwise we need to wait until process becomes warm + final boolean processWarm = queue.isProcessWarm(); + if (processWarm) { + notifyStartedRunning(queue); + } - // Tell other OS components that app is not actively running, giving - // a chance to update OOM adjustment - notifyStoppedRunning(queue); + // If we're already warm, schedule next pending broadcast now; + // otherwise we'll wait for the cold start to circle back around + queue.makeActiveNextPending(); + if (processWarm) { + queue.traceProcessRunningBegin(); + } else { + queue.traceProcessStartingBegin(); + } + } + + /** + * Demote a process from the "running" list. + */ + @GuardedBy("mService") + private void demoteFromRunningLocked(@NonNull BroadcastProcessQueue queue) { + if (!queue.isActive()) { + logw("Ignoring demoteFromRunning; no active broadcast for " + queue); + return; } + + final int cookie = traceBegin("demoteFromRunning"); + // We've drained running broadcasts; maybe move back to runnable + queue.makeActiveIdle(); + queue.traceProcessEnd(); + + final int queueIndex = getRunningIndexOf(queue); + mRunning[queueIndex] = null; + updateRunnableList(queue); + enqueueUpdateRunningList(); + + // Tell other OS components that app is not actively running, giving + // a chance to update OOM adjustment + notifyStoppedRunning(queue); traceEnd(cookie); - return res; } /** @@ -1212,11 +1295,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return UserHandle.getUserId(q.uid) == userId; }; broadcastPredicate = BROADCAST_PREDICATE_ANY; + + cleanupUserStateLocked(mUidCached, userId); + cleanupUserStateLocked(mUidForeground, userId); } return forEachMatchingBroadcast(queuePredicate, broadcastPredicate, mBroadcastConsumerSkip, true); } + @GuardedBy("mService") + private void cleanupUserStateLocked(@NonNull SparseBooleanArray uidState, int userId) { + for (int i = uidState.size() - 1; i >= 0; --i) { + final int uid = uidState.keyAt(i); + if (UserHandle.getUserId(uid) == userId) { + uidState.removeAt(i); + } + } + } + private static final Predicate<BroadcastProcessQueue> QUEUE_PREDICATE_ANY = (q) -> true; private static final BroadcastPredicate BROADCAST_PREDICATE_ANY = @@ -1332,6 +1428,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mService.registerUidObserver(new UidObserver() { @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, + int capability) { + synchronized (mService) { + if (procState == ActivityManager.PROCESS_STATE_TOP) { + mUidForeground.put(uid, true); + } else { + mUidForeground.delete(uid); + } + refreshProcessQueuesLocked(uid); + } + } + + @Override public void onUidCachedChanged(int uid, boolean cached) { synchronized (mService) { if (cached) { @@ -1339,18 +1448,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } else { mUidCached.delete(uid); } - - BroadcastProcessQueue leaf = mProcessQueues.get(uid); - while (leaf != null) { - // Update internal state by refreshing values previously - // read from any known running process - setQueueProcess(leaf, leaf.app); - leaf = leaf.processNameNext; - } - enqueueUpdateRunningList(); + refreshProcessQueuesLocked(uid); } } - }, ActivityManager.UID_OBSERVER_CACHED, 0, "android"); + }, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_CACHED, + ActivityManager.PROCESS_STATE_TOP, "android"); // Kick off periodic health checks mLocalHandler.sendEmptyMessage(MSG_CHECK_HEALTH); @@ -1376,6 +1478,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } @Override + public boolean isDispatchedLocked(@NonNull Intent intent) { + return isDispatchedLocked(intent, LOG_WRITER_INFO); + } + + public boolean isDispatchedLocked(@NonNull Intent intent, @NonNull PrintWriter pw) { + return testAllProcessQueues(q -> q.isDispatched(intent), + "dispatch of " + intent, pw); + } + + @Override public void waitForIdle(@NonNull PrintWriter pw) { waitFor(() -> isIdleLocked(pw)); } @@ -1383,28 +1495,35 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @Override public void waitForBarrier(@NonNull PrintWriter pw) { final long now = SystemClock.uptimeMillis(); - waitFor(() -> isBeyondBarrierLocked(now, pw)); + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.addPrioritizeEarliestRequest()); + } + try { + waitFor(() -> isBeyondBarrierLocked(now, pw)); + } finally { + synchronized (mService) { + forEachMatchingQueue(QUEUE_PREDICATE_ANY, + q -> q.removePrioritizeEarliestRequest()); + } + } + } + + @Override + public void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw) { + waitFor(() -> isDispatchedLocked(intent, pw)); } private void waitFor(@NonNull BooleanSupplier condition) { final CountDownLatch latch = new CountDownLatch(1); synchronized (mService) { mWaitingFor.add(Pair.create(condition, latch)); - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(true)); } enqueueUpdateRunningList(); try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); - } finally { - synchronized (mService) { - if (mWaitingFor.isEmpty()) { - forEachMatchingQueue(QUEUE_PREDICATE_ANY, - (q) -> q.setPrioritizeEarliest(false)); - } - } } } @@ -1522,8 +1641,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // warm via this operation, we're going to immediately promote it to // be running, and any side effect of this operation will then apply // after it's finished and is returned to the runnable list. - queue.setProcessAndUidCached( + queue.setProcessAndUidState( mService.getProcessRecordLocked(queue.processName, queue.uid), + mUidForeground.get(queue.uid, false), mUidCached.get(queue.uid, false)); } } @@ -1535,12 +1655,29 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @Nullable ProcessRecord app) { - if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) { + if (queue.setProcessAndUidState(app, mUidForeground.get(queue.uid, false), + mUidCached.get(queue.uid, false))) { updateRunnableList(queue); } } /** + * Refresh the process queues with the latest process state so that runnableAt + * can be updated. + */ + @GuardedBy("mService") + private void refreshProcessQueuesLocked(int uid) { + BroadcastProcessQueue leaf = mProcessQueues.get(uid); + while (leaf != null) { + // Update internal state by refreshing values previously + // read from any known running process + setQueueProcess(leaf, leaf.app); + leaf = leaf.processNameNext; + } + enqueueUpdateRunningList(); + } + + /** * Inform other parts of OS that the given broadcast queue has started * running, typically for internal bookkeeping. */ @@ -1861,7 +1998,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ipw.println("Cached UIDs:"); ipw.increaseIndent(); - ipw.println(mUidCached.toString()); + ipw.println(mUidCached); + ipw.decreaseIndent(); + ipw.println(); + + ipw.println("Foreground UIDs:"); + ipw.increaseIndent(); + ipw.println(mUidForeground); ipw.decreaseIndent(); ipw.println(); diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 64fe39314f0e..a92744086f70 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -237,6 +237,14 @@ final class BroadcastRecord extends Binder { } } + /** + * Return true if this receiver should be assumed to have been delivered. + */ + boolean isAssumedDelivered(int index) { + return (receivers.get(index) instanceof BroadcastFilter) && !ordered + && (resultTo == null); + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 4ec813ecd81c..335d6768c37b 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -1072,11 +1072,6 @@ class ProcessRecord implements WindowProcessListener { return mState.isCached(); } - @GuardedBy(anyOf = {"mService", "mProcLock"}) - public boolean hasForegroundActivities() { - return mState.hasForegroundActivities(); - } - boolean hasActivities() { return mWindowProcessController.hasActivities(); } diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java index 5e41dcd0009e..a2582083c409 100644 --- a/services/core/java/com/android/server/am/UidObserverController.java +++ b/services/core/java/com/android/server/am/UidObserverController.java @@ -557,7 +557,7 @@ public class UidObserverController { return true; } - return Arrays.binarySearch(mUids, uid) != -1; + return Arrays.binarySearch(mUids, uid) >= 0; } void addUid(int uid) { diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java index a9a77bf28ebe..c6b15b6dcd7a 100644 --- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -60,6 +60,7 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * System service for managing {@link AmbientContextEvent}s. @@ -595,7 +596,7 @@ public class AmbientContextManagerService extends synchronized (mLock) { for (ClientRequest cr : mExistingClientRequests) { - if (cr.getPackageName().equals(callingPackage)) { + if ((cr != null) && cr.getPackageName().equals(callingPackage)) { AmbientContextManagerPerUserService service = getAmbientContextManagerPerUserServiceForEventTypes( UserHandle.getCallingUserId(), diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 20393250b5b7..31403871ac90 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -624,7 +624,7 @@ public class AudioService extends IAudioService.Stub private int mZenModeAffectedStreams = 0; // Streams currently muted by ringer mode and dnd - private int mRingerAndZenModeMutedStreams; + protected static volatile int sRingerAndZenModeMutedStreams; /** Streams that can be muted. Do not resolve to aliases when checking. * @see System#MUTE_STREAMS_AFFECTED */ @@ -1320,7 +1320,9 @@ public class AudioService extends IAudioService.Stub // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. - mRingerAndZenModeMutedStreams = 0; + sRingerAndZenModeMutedStreams = 0; + sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent( + sRingerAndZenModeMutedStreams, "onInitStreamsAndVolumes")); setRingerModeInt(getRingerModeInternal(), false); final float[] preScale = new float[3]; @@ -2132,7 +2134,7 @@ public class AudioService extends IAudioService.Stub // Unmute streams if required and device is full volume if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) { - mStreamStates[streamType].mute(false); + mStreamStates[streamType].mute(false, "updateVolumeStates(" + caller); } } } @@ -3681,7 +3683,7 @@ public class AudioService extends IAudioService.Stub if (!(mCameraSoundForced && (vss.getStreamType() == AudioSystem.STREAM_SYSTEM_ENFORCED))) { - boolean changed = vss.mute(state, /* apply= */ false); + boolean changed = vss.mute(state, /* apply= */ false, "muteAliasStreams"); if (changed) { streamsToMute.add(stream); } @@ -3708,7 +3710,8 @@ public class AudioService extends IAudioService.Stub boolean wasMuted; synchronized (VolumeStreamState.class) { final VolumeStreamState streamState = mStreamStates[stream]; - wasMuted = streamState.mute(false); // if unmuting causes a change, it was muted + // if unmuting causes a change, it was muted + wasMuted = streamState.mute(false, "onUnmuteStream"); final int device = getDeviceForStream(stream); final int index = streamState.getIndex(device); @@ -3801,13 +3804,13 @@ public class AudioService extends IAudioService.Stub /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device, String caller, boolean hasModifyAudioSettings, boolean canChangeMute) { final int stream = mStreamVolumeAlias[streamType]; - setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings); // setting volume on ui sounds stream type also controls silent mode if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (stream == getUiSoundsStreamType())) { setRingerMode(getNewRingerMode(stream, index, flags), TAG + ".onSetStreamVolume", false /*external*/); } + setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings); // setting non-zero volume for a muted stream unmutes the stream and vice versa // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) { @@ -5498,12 +5501,16 @@ public class AudioService extends IAudioService.Stub PERSIST_DELAY); } } - mStreamStates[streamType].mute(false); - mRingerAndZenModeMutedStreams &= ~(1 << streamType); + sRingerAndZenModeMutedStreams &= ~(1 << streamType); + sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent( + sRingerAndZenModeMutedStreams, "muteRingerModeStreams")); + mStreamStates[streamType].mute(false, "muteRingerModeStreams"); } else { // mute - mStreamStates[streamType].mute(true); - mRingerAndZenModeMutedStreams |= (1 << streamType); + sRingerAndZenModeMutedStreams |= (1 << streamType); + sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent( + sRingerAndZenModeMutedStreams, "muteRingerModeStreams")); + mStreamStates[streamType].mute(true, "muteRingerModeStreams"); } } } @@ -6702,7 +6709,7 @@ public class AudioService extends IAudioService.Stub } private boolean isStreamMutedByRingerOrZenMode(int streamType) { - return (mRingerAndZenModeMutedStreams & (1 << streamType)) != 0; + return (sRingerAndZenModeMutedStreams & (1 << streamType)) != 0; } /** @@ -7613,7 +7620,7 @@ public class AudioService extends IAudioService.Stub Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]", newDevice, AudioSystem.getOutputDeviceName(newDevice))); } - mStreamStates[AudioSystem.STREAM_MUSIC].mute(false); + mStreamStates[AudioSystem.STREAM_MUSIC].mute(false, "onAccessoryPlugMediaUnmute"); } } @@ -7989,7 +7996,8 @@ public class AudioService extends IAudioService.Stub true /*hasModifyAudioSettings*/); } if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { - mStreamStates[stream].mute(isMuted()); + mStreamStates[stream].mute(isMuted(), + "VGS.applyAllVolumes#1"); } } } @@ -8030,7 +8038,7 @@ public class AudioService extends IAudioService.Stub true /*hasModifyAudioSettings*/); } if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { - mStreamStates[stream].mute(isMuted()); + mStreamStates[stream].mute(isMuted(), "VGS.applyAllVolumes#2"); } } } @@ -8718,10 +8726,10 @@ public class AudioService extends IAudioService.Stub * @param state the new mute state * @return true if the mute state was changed */ - public boolean mute(boolean state) { + public boolean mute(boolean state, String source) { boolean changed = false; synchronized (VolumeStreamState.class) { - changed = mute(state, true); + changed = mute(state, true, source); } if (changed) { broadcastMuteSetting(mStreamType, state); @@ -8770,10 +8778,21 @@ public class AudioService extends IAudioService.Stub * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume} * @return true if the mute state was changed */ - public boolean mute(boolean state, boolean apply) { + public boolean mute(boolean state, boolean apply, String src) { synchronized (VolumeStreamState.class) { boolean changed = state != mIsMuted; if (changed) { + sMuteLogger.enqueue( + new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src)); + // check to see if unmuting should not have happened due to ringer muted streams + if (!state && isStreamMutedByRingerOrZenMode(mStreamType)) { + Log.e(TAG, "Unmuting stream " + mStreamType + + " despite ringer-zen muted stream 0x" + + Integer.toHexString(AudioService.sRingerAndZenModeMutedStreams), + new Exception()); // this will put a stack trace in the logs + sMuteLogger.enqueue(new AudioServiceEvents.StreamUnmuteErrorEvent( + mStreamType, AudioService.sRingerAndZenModeMutedStreams)); + } mIsMuted = state; if (apply) { doMute(); @@ -9378,9 +9397,9 @@ public class AudioService extends IAudioService.Stub public void onChange(boolean selfChange) { super.onChange(selfChange); // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode. - // However there appear to be some missing locks around mRingerAndZenModeMutedStreams + // However there appear to be some missing locks around sRingerAndZenModeMutedStreams // and mRingerModeAffectedStreams, so will leave this synchronized for now. - // mRingerAndZenModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). + // sRingerAndZenModeMutedStreams and mMuteAffectedStreams are safe (only accessed once). synchronized (mSettingsLock) { if (updateRingerAndZenModeAffectedStreams()) { /* @@ -10873,6 +10892,9 @@ public class AudioService extends IAudioService.Stub sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE, "audio services lifecycle"); + static final EventLogger sMuteLogger = new EventLogger(30, + "mute commands"); + final private EventLogger mModeLogger = new EventLogger(LOG_NB_EVENTS_PHONE_STATE, "phone state (logged after successful call to AudioSystem.setPhoneState(int, int))"); @@ -10913,7 +10935,7 @@ public class AudioService extends IAudioService.Stub pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]); pw.println("- zen mode:" + Settings.Global.zenModeToString(mNm.getZenMode())); dumpRingerModeStreams(pw, "affected", mRingerModeAffectedStreams); - dumpRingerModeStreams(pw, "muted", mRingerAndZenModeMutedStreams); + dumpRingerModeStreams(pw, "muted", sRingerAndZenModeMutedStreams); pw.print("- delegate = "); pw.println(mRingerModeDelegate); } @@ -11039,6 +11061,8 @@ public class AudioService extends IAudioService.Stub pw.println("\n"); sVolumeLogger.dump(pw); pw.println("\n"); + sMuteLogger.dump(pw); + pw.println("\n"); dumpSupportedSystemUsage(pw); pw.println("\n"); diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index b022b5bb866a..6ad9390ef366 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -563,4 +563,75 @@ public class AudioServiceEvents { return new StringBuilder("FIXME invalid event type:").append(mEventType).toString(); } } + + /** + * Class to log stream type mute/unmute events + */ + static final class StreamMuteEvent extends EventLogger.Event { + final int mStreamType; + final boolean mMuted; + final String mSource; + + StreamMuteEvent(int streamType, boolean muted, String source) { + mStreamType = streamType; + mMuted = muted; + mSource = source; + } + + @Override + public String eventToString() { + final String streamName = + (mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0) + ? AudioSystem.STREAM_NAMES[mStreamType] + : ("stream " + mStreamType); + return new StringBuilder(streamName) + .append(mMuted ? " muting by " : " unmuting by ") + .append(mSource) + .toString(); + } + } + + /** + * Class to log unmute errors that contradict the ringer/zen mode muted streams + */ + static final class StreamUnmuteErrorEvent extends EventLogger.Event { + final int mStreamType; + final int mRingerZenMutedStreams; + + StreamUnmuteErrorEvent(int streamType, int ringerZenMutedStreams) { + mStreamType = streamType; + mRingerZenMutedStreams = ringerZenMutedStreams; + } + + @Override + public String eventToString() { + final String streamName = + (mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0) + ? AudioSystem.STREAM_NAMES[mStreamType] + : ("stream " + mStreamType); + return new StringBuilder("Error trying to unmute ") + .append(streamName) + .append(" despite muted streams 0x") + .append(Integer.toHexString(mRingerZenMutedStreams)) + .toString(); + } + } + + static final class RingerZenMutedStreamsEvent extends EventLogger.Event { + final int mRingerZenMutedStreams; + final String mSource; + + RingerZenMutedStreamsEvent(int ringerZenMutedStreams, String source) { + mRingerZenMutedStreams = ringerZenMutedStreams; + mSource = source; + } + + @Override + public String eventToString() { + return new StringBuilder("RingerZenMutedStreams 0x") + .append(Integer.toHexString(mRingerZenMutedStreams)) + .append(" from ").append(mSource) + .toString(); + } + } } diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java index 00af22468fd3..2ba59b0d659a 100644 --- a/services/core/java/com/android/server/devicestate/DeviceState.java +++ b/services/core/java/com/android/server/devicestate/DeviceState.java @@ -139,7 +139,10 @@ public final class DeviceState { @Override public String toString() { return "DeviceState{" + "identifier=" + mIdentifier + ", name='" + mName + '\'' - + ", app_accessible=" + !hasFlag(FLAG_APP_INACCESSIBLE) + "}"; + + ", app_accessible=" + !hasFlag(FLAG_APP_INACCESSIBLE) + + ", cancel_when_requester_not_on_top=" + + hasFlag(FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP) + + "}"; } @Override diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java new file mode 100644 index 000000000000..fba2aa60b952 --- /dev/null +++ b/services/core/java/com/android/server/input/FocusEventDebugView.java @@ -0,0 +1,343 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input; + +import static android.util.TypedValue.COMPLEX_UNIT_DIP; +import static android.util.TypedValue.COMPLEX_UNIT_SP; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import android.animation.LayoutTransition; +import android.annotation.AnyThread; +import android.content.Context; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Typeface; +import android.util.Pair; +import android.util.Slog; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.RoundedCorner; +import android.view.View; +import android.view.WindowInsets; +import android.view.animation.AccelerateInterpolator; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.internal.R; + +import java.util.HashMap; +import java.util.Map; + +/** + * Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on + * the screen. + */ +class FocusEventDebugView extends LinearLayout { + + private static final String TAG = FocusEventDebugView.class.getSimpleName(); + + private static final int KEY_FADEOUT_DURATION_MILLIS = 1000; + private static final int KEY_TRANSITION_DURATION_MILLIS = 100; + + private static final int OUTER_PADDING_DP = 16; + private static final int KEY_SEPARATION_MARGIN_DP = 16; + private static final int KEY_VIEW_SIDE_PADDING_DP = 16; + private static final int KEY_VIEW_VERTICAL_PADDING_DP = 8; + private static final int KEY_VIEW_MIN_WIDTH_DP = 32; + private static final int KEY_VIEW_TEXT_SIZE_SP = 12; + + private final int mOuterPadding; + + // Tracks all keys that are currently pressed/down. + private final Map<Pair<Integer /*deviceId*/, Integer /*scanCode*/>, PressedKeyView> + mPressedKeys = new HashMap<>(); + + private final PressedKeyContainer mPressedKeyContainer; + private final PressedKeyContainer mPressedModifierContainer; + + FocusEventDebugView(Context c) { + super(c); + setFocusableInTouchMode(true); + + final var dm = mContext.getResources().getDisplayMetrics(); + mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm); + + setOrientation(HORIZONTAL); + setLayoutDirection(LAYOUT_DIRECTION_RTL); + setGravity(Gravity.START | Gravity.BOTTOM); + + mPressedKeyContainer = new PressedKeyContainer(mContext); + mPressedKeyContainer.setOrientation(HORIZONTAL); + mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM); + mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR); + final var scroller = new HorizontalScrollView(mContext); + scroller.addView(mPressedKeyContainer); + scroller.setHorizontalScrollBarEnabled(false); + scroller.addOnLayoutChangeListener( + (view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT)); + scroller.setHorizontalFadingEdgeEnabled(true); + addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1)); + + mPressedModifierContainer = new PressedKeyContainer(mContext); + mPressedModifierContainer.setOrientation(VERTICAL); + mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM); + addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + int paddingBottom = 0; + + final RoundedCorner bottomLeft = + insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); + if (bottomLeft != null) { + paddingBottom = bottomLeft.getRadius(); + } + + final RoundedCorner bottomRight = + insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT); + if (bottomRight != null) { + paddingBottom = Math.max(paddingBottom, bottomRight.getRadius()); + } + + if (insets.getDisplayCutout() != null) { + paddingBottom = + Math.max(paddingBottom, insets.getDisplayCutout().getSafeInsetBottom()); + } + + setPadding(mOuterPadding, mOuterPadding, mOuterPadding, mOuterPadding + paddingBottom); + setClipToPadding(false); + invalidate(); + return super.onApplyWindowInsets(insets); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + handleKeyEvent(event); + return super.dispatchKeyEvent(event); + } + + /** Report an input event to the debug view. */ + @AnyThread + public void reportEvent(InputEvent event) { + if (!(event instanceof KeyEvent)) { + // TODO: Support non-pointer MotionEvents. + return; + } + post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event))); + } + + private void handleKeyEvent(KeyEvent keyEvent) { + final var identifier = new Pair<>(keyEvent.getDeviceId(), keyEvent.getScanCode()); + final var container = KeyEvent.isModifierKey(keyEvent.getKeyCode()) + ? mPressedModifierContainer + : mPressedKeyContainer; + PressedKeyView pressedKeyView = mPressedKeys.get(identifier); + switch (keyEvent.getAction()) { + case KeyEvent.ACTION_DOWN: { + if (pressedKeyView != null) { + if (keyEvent.getRepeatCount() == 0) { + Slog.w(TAG, "Got key down for " + + KeyEvent.keyCodeToString(keyEvent.getKeyCode()) + + " that was already tracked as being down."); + break; + } + container.handleKeyRepeat(pressedKeyView); + break; + } + + pressedKeyView = new PressedKeyView(mContext, getLabel(keyEvent)); + mPressedKeys.put(identifier, pressedKeyView); + container.handleKeyPressed(pressedKeyView); + break; + } + case KeyEvent.ACTION_UP: { + if (pressedKeyView == null) { + Slog.w(TAG, "Got key up for " + KeyEvent.keyCodeToString(keyEvent.getKeyCode()) + + " that was not tracked as being down."); + break; + } + mPressedKeys.remove(identifier); + container.handleKeyRelease(pressedKeyView); + break; + } + default: + break; + } + keyEvent.recycle(); + } + + private static String getLabel(KeyEvent event) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_SPACE: + return "\u2423"; + case KeyEvent.KEYCODE_TAB: + return "\u21e5"; + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_NUMPAD_ENTER: + return "\u23CE"; + case KeyEvent.KEYCODE_DEL: + return "\u232B"; + case KeyEvent.KEYCODE_FORWARD_DEL: + return "\u2326"; + case KeyEvent.KEYCODE_ESCAPE: + return "ESC"; + case KeyEvent.KEYCODE_DPAD_UP: + return "\u2191"; + case KeyEvent.KEYCODE_DPAD_DOWN: + return "\u2193"; + case KeyEvent.KEYCODE_DPAD_LEFT: + return "\u2190"; + case KeyEvent.KEYCODE_DPAD_RIGHT: + return "\u2192"; + case KeyEvent.KEYCODE_DPAD_UP_RIGHT: + return "\u2197"; + case KeyEvent.KEYCODE_DPAD_UP_LEFT: + return "\u2196"; + case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT: + return "\u2198"; + case KeyEvent.KEYCODE_DPAD_DOWN_LEFT: + return "\u2199"; + default: + break; + } + + final int unicodeChar = event.getUnicodeChar(); + if (unicodeChar != 0) { + return new String(Character.toChars(unicodeChar)); + } + + final var label = KeyEvent.keyCodeToString(event.getKeyCode()); + if (label.startsWith("KEYCODE_")) { + return label.substring(8); + } + return label; + } + + private static class PressedKeyView extends TextView { + + private static final ColorFilter sInvertColors = new ColorMatrixColorFilter(new float[]{ + -1.0f, 0, 0, 0, 255, // red + 0, -1.0f, 0, 0, 255, // green + 0, 0, -1.0f, 0, 255, // blue + 0, 0, 0, 1.0f, 0 // alpha + }); + + PressedKeyView(Context c, String label) { + super(c); + + final var dm = c.getResources().getDisplayMetrics(); + final int keyViewSidePadding = + (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_SIDE_PADDING_DP, dm); + final int keyViewVerticalPadding = + (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_VERTICAL_PADDING_DP, + dm); + final int keyViewMinWidth = + (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_MIN_WIDTH_DP, dm); + final int textSize = + (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, KEY_VIEW_TEXT_SIZE_SP, dm); + + setText(label); + setGravity(Gravity.CENTER); + setMinimumWidth(keyViewMinWidth); + setTextSize(textSize); + setTypeface(Typeface.SANS_SERIF); + setBackgroundResource(R.drawable.focus_event_pressed_key_background); + setPaddingRelative(keyViewSidePadding, keyViewVerticalPadding, keyViewSidePadding, + keyViewVerticalPadding); + + setHighlighted(true); + } + + void setHighlighted(boolean isHighlighted) { + if (isHighlighted) { + setTextColor(Color.BLACK); + getBackground().setColorFilter(sInvertColors); + } else { + setTextColor(Color.WHITE); + getBackground().clearColorFilter(); + } + invalidate(); + } + } + + private static class PressedKeyContainer extends LinearLayout { + + private final MarginLayoutParams mPressedKeyLayoutParams; + + PressedKeyContainer(Context c) { + super(c); + + final var dm = c.getResources().getDisplayMetrics(); + final int keySeparationMargin = + (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_SEPARATION_MARGIN_DP, dm); + + final var transition = new LayoutTransition(); + transition.disableTransitionType(LayoutTransition.APPEARING); + transition.disableTransitionType(LayoutTransition.DISAPPEARING); + transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); + transition.setDuration(KEY_TRANSITION_DURATION_MILLIS); + setLayoutTransition(transition); + + mPressedKeyLayoutParams = new MarginLayoutParams(WRAP_CONTENT, WRAP_CONTENT); + if (getOrientation() == VERTICAL) { + mPressedKeyLayoutParams.setMargins(0, keySeparationMargin, 0, 0); + } else { + mPressedKeyLayoutParams.setMargins(keySeparationMargin, 0, 0, 0); + } + } + + public void handleKeyPressed(PressedKeyView pressedKeyView) { + addView(pressedKeyView, getChildCount(), mPressedKeyLayoutParams); + invalidate(); + } + + public void handleKeyRepeat(PressedKeyView repeatedKeyView) { + // Do nothing for now. + } + + public void handleKeyRelease(PressedKeyView releasedKeyView) { + releasedKeyView.setHighlighted(false); + releasedKeyView.clearAnimation(); + releasedKeyView.animate() + .alpha(0) + .setDuration(KEY_FADEOUT_DURATION_MILLIS) + .setInterpolator(new AccelerateInterpolator()) + .withEndAction(this::cleanUpPressedKeyViews) + .start(); + } + + private void cleanUpPressedKeyViews() { + int numChildrenToRemove = 0; + for (int i = 0; i < getChildCount(); i++) { + final View child = getChildAt(i); + if (child.getAlpha() != 0) { + break; + } + child.setVisibility(View.GONE); + child.clearAnimation(); + numChildrenToRemove++; + } + removeViews(0, numChildrenToRemove); + invalidate(); + } + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 5f45f912a87a..662591e3d264 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -18,6 +18,7 @@ package com.android.server.input; import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT; import static android.view.KeyEvent.KEYCODE_UNKNOWN; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import android.Manifest; import android.annotation.EnforcePermission; @@ -32,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.graphics.PixelFormat; import android.graphics.PointF; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.Sensors; @@ -100,6 +102,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.VerifiedInputEvent; import android.view.ViewConfiguration; +import android.view.WindowManager; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -386,6 +389,11 @@ public class InputManagerService extends IInputManager.Stub /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */ final boolean mUseDevInputEventForAudioJack; + private final Object mFocusEventDebugViewLock = new Object(); + @GuardedBy("mFocusEventDebugViewLock") + @Nullable + private FocusEventDebugView mFocusEventDebugView; + /** Point of injection for test dependencies. */ @VisibleForTesting static class Injector { @@ -427,7 +435,7 @@ public class InputManagerService extends IInputManager.Stub mContext = injector.getContext(); mHandler = new InputManagerHandler(injector.getLooper()); mNative = injector.getNativeService(this); - mSettingsObserver = new InputSettingsObserver(mContext, mHandler, mNative); + mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative); mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore, injector.getLooper()); mBatteryController = new BatteryController(mContext, mNative, injector.getLooper()); @@ -2460,6 +2468,11 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) { + synchronized (mFocusEventDebugViewLock) { + if (mFocusEventDebugView != null) { + mFocusEventDebugView.reportEvent(event); + } + } return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags); } @@ -3367,6 +3380,45 @@ public class InputManagerService extends IInputManager.Stub } } + void updateFocusEventDebugViewEnabled(boolean enabled) { + FocusEventDebugView view; + synchronized (mFocusEventDebugViewLock) { + if (enabled == (mFocusEventDebugView != null)) { + return; + } + if (enabled) { + mFocusEventDebugView = new FocusEventDebugView(mContext); + view = mFocusEventDebugView; + } else { + view = mFocusEventDebugView; + mFocusEventDebugView = null; + } + } + Objects.requireNonNull(view); + + // Interact with WM outside the lock, since the lock is part of the input hotpath. + final WindowManager wm = + Objects.requireNonNull(mContext.getSystemService(WindowManager.class)); + if (!enabled) { + wm.removeView(view); + return; + } + + // TODO: Support multi display + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY; + lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setFitInsetsTypes(0); + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle("FocusEventDebugView - display " + mContext.getDisplayId()); + lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; + wm.addView(view, lp); + } + interface KeyboardBacklightControllerInterface { default void incrementKeyboardBacklight(int deviceId) {} default void decrementKeyboardBacklight(int deviceId) {} diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 153e9c174b85..651063e8841b 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -43,13 +43,16 @@ class InputSettingsObserver extends ContentObserver { private final Context mContext; private final Handler mHandler; + private final InputManagerService mService; private final NativeInputManagerService mNative; private final Map<Uri, Consumer<String /* reason*/>> mObservers; - InputSettingsObserver(Context context, Handler handler, NativeInputManagerService nativeIms) { + InputSettingsObserver(Context context, Handler handler, InputManagerService service, + NativeInputManagerService nativeIms) { super(handler); mContext = context; mHandler = handler; + mService = service; mNative = nativeIms; mObservers = Map.ofEntries( Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED), @@ -72,7 +75,9 @@ class InputSettingsObserver extends ContentObserver { Map.entry( Settings.Global.getUriFor( Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH), - (reason) -> updateMaximumObscuringOpacityForTouch())); + (reason) -> updateMaximumObscuringOpacityForTouch()), + Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES), + (reason) -> updateShowKeyPresses())); } /** @@ -145,6 +150,11 @@ class InputSettingsObserver extends ContentObserver { mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false)); } + private void updateShowKeyPresses() { + mService.updateFocusEventDebugViewEnabled( + getBoolean(Settings.System.SHOW_KEY_PRESSES, false)); + } + private void updateAccessibilityLargePointer() { final int accessibilityConfig = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON, diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index effef47e21d6..6a0550b60c15 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -55,8 +55,7 @@ import java.util.OptionalInt; final class HandwritingModeController { public static final String TAG = HandwritingModeController.class.getSimpleName(); - // TODO(b/210039666): flip the flag. - static final boolean DEBUG = true; + static final boolean DEBUG = false; // Use getHandwritingBufferSize() and not this value directly. private static final int EVENT_BUFFER_SIZE = 100; // A longer event buffer used for handwriting delegation diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java index 82b4da3850f4..ed5c1306733e 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -272,6 +272,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private long mStartedChangedElapsedRealtime; private int mFixInterval = 1000; + // True if handleInitialize() has finished; + @GuardedBy("mLock") + private boolean mInitialized; + private ProviderRequest mProviderRequest; private int mPositionMode; @@ -570,6 +574,9 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } updateEnabled(); + synchronized (mLock) { + mInitialized = true; + } } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -1718,8 +1725,12 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } // Re-register network callbacks to get an update of available networks right away. - mNetworkConnectivityHandler.unregisterNetworkCallbacks(); - mNetworkConnectivityHandler.registerNetworkCallbacks(); + synchronized (mLock) { + if (mInitialized) { + mNetworkConnectivityHandler.unregisterNetworkCallbacks(); + mNetworkConnectivityHandler.registerNetworkCallbacks(); + } + } } @Override diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java index a7fffe2ddf29..c0cce6f32029 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java +++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java @@ -303,6 +303,7 @@ class GnssNetworkConnectivityHandler { void unregisterNetworkCallbacks() { mConnMgr.unregisterNetworkCallback(mNetworkConnectivityCallback); + mNetworkConnectivityCallback = null; } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index f0737566fd66..24dbce49eace 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -128,18 +128,14 @@ public class RecoverableKeyStoreManager { public static synchronized RecoverableKeyStoreManager getInstance(Context context) { if (mInstance == null) { - RecoverableKeyStoreDb db; + RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context); RemoteLockscreenValidationSessionStorage lockscreenCheckSessions; if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) { - // TODO(b/254335492): Remove flag check when feature is launched. - db = RecoverableKeyStoreDb.newInstance(context, 7); lockscreenCheckSessions = new RemoteLockscreenValidationSessionStorage(); } else { - db = RecoverableKeyStoreDb.newInstance(context); lockscreenCheckSessions = null; } - PlatformKeyManager platformKeyManager; ApplicationKeyStorage applicationKeyStorage; try { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java index 4a17e9a9242e..d881769ca380 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -78,18 +78,6 @@ public class RecoverableKeyStoreDb { return new RecoverableKeyStoreDb(helper); } - /** - * A new instance, storing the database in the user directory of {@code context}. - * - * @hide - */ - public static RecoverableKeyStoreDb newInstance(Context context, int version) { - RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context, version); - helper.setWriteAheadLoggingEnabled(true); - helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); - return new RecoverableKeyStoreDb(helper); - } - private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { this.mKeyStoreDbHelper = keyStoreDbHelper; this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper(); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java index 0e5e55c2d7b0..386655ae41d0 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java @@ -34,7 +34,6 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { private static final String TAG = "RecoverableKeyStoreDbHp"; // v6 - added user id serial number. - static final int DATABASE_VERSION = 6; // v7 - added bad guess counter for remote LSKF check; static final int DATABASE_VERSION_7 = 7; private static final String DATABASE_NAME = "recoverablekeystore.db"; @@ -118,23 +117,14 @@ class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { super(context, DATABASE_NAME, null, getDbVersion(context)); } - RecoverableKeyStoreDbHelper(Context context, int version) { - super(context, DATABASE_NAME, null, version); - } - private static int getDbVersion(Context context) { - // TODO(b/254335492): Update to version 7 and clean up code. - return DATABASE_VERSION; + return DATABASE_VERSION_7; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_KEYS_ENTRY); - if (db.getVersion() == 6) { // always false - db.execSQL(SQL_CREATE_USER_METADATA_ENTRY); - } else { - db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7); - } + db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7); db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY); db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY); } diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index ac8ff21b7fa6..ba5907c4b4d0 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -256,6 +256,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, private final PackageManagerInternal mPmInternal; private volatile boolean mFeatureEnabled = PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT; + @GuardedBy("mDisabledPackages") private final ArraySet<String> mDisabledPackages = new ArraySet<>(); @Nullable @@ -272,7 +273,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, mInjector = null; mPmInternal = null; mFeatureEnabled = orig.mFeatureEnabled; - mDisabledPackages.addAll(orig.mDisabledPackages); + synchronized (orig.mDisabledPackages) { + mDisabledPackages.addAll(orig.mDisabledPackages); + } mLoggingEnabled = orig.mLoggingEnabled; } @@ -319,7 +322,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled"); } try { - return !mDisabledPackages.contains(pkg.getPackageName()); + synchronized (mDisabledPackages) { + return !mDisabledPackages.contains(pkg.getPackageName()); + } } finally { if (DEBUG_TRACING) { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); @@ -376,10 +381,12 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternalNoLogging( PackageManager.FILTER_APPLICATION_QUERY, AndroidPackageUtils.generateAppInfoWithoutState(pkg)); - if (enabled) { - mDisabledPackages.remove(pkg.getPackageName()); - } else { - mDisabledPackages.add(pkg.getPackageName()); + synchronized (mDisabledPackages) { + if (enabled) { + mDisabledPackages.remove(pkg.getPackageName()); + } else { + mDisabledPackages.add(pkg.getPackageName()); + } } if (mAppsFilter != null) { mAppsFilter.onChanged(); @@ -393,7 +400,9 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, || setting.getPkg().isDebuggable()); enableLogging(setting.getAppId(), enableLogging); if (removed) { - mDisabledPackages.remove(setting.getPackageName()); + synchronized (mDisabledPackages) { + mDisabledPackages.remove(setting.getPackageName()); + } if (mAppsFilter != null) { mAppsFilter.onChanged(); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1721f83538ff..a3651946da12 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1316,6 +1316,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final var snapshot = mPm.snapshotComputer(); final int callingUid = Binder.getCallingUid(); + final var callingPackageName = snapshot.getNameForUid(callingUid); + if (!TextUtils.equals(callingPackageName, installerPackageName)) { + throw new SecurityException("The installerPackageName set by the caller doesn't match " + + "the caller's own package name."); + } if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) { for (var packageName : packageNames) { var ps = snapshot.getPackageStateInternal(packageName); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 3492b2660c4a..f41d964bf906 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,7 +23,6 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT; -import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; @@ -3657,25 +3656,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (String permission : pkg.getRequestedPermissions()) { Integer permissionState = permissionStates.get(permission); - if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT) - && permissionState == null) { - final PackageStateInternal ps; - final long token = Binder.clearCallingIdentity(); - try { - ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); - } finally { - Binder.restoreCallingIdentity(token); - } - final String[] useFullScreenIntentPackageNames = - mContext.getResources().getStringArray( - com.android.internal.R.array.config_useFullScreenIntentPackages); - final boolean canUseFullScreenIntent = (ps != null && ps.isSystem()) - || ArrayUtils.contains(useFullScreenIntentPackageNames, - pkg.getPackageName()); - permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED - : PERMISSION_STATE_DENIED; - } - if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) { continue; } 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 661715c0eb12..93d6676dd929 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -4745,17 +4745,19 @@ public class BatteryStatsImpl extends BatteryStats { requestWakelockCpuUpdate(); } - getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs) - .noteStartWakeLocked(pid, name, type, elapsedRealtimeMs); + Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs); + uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs); + + int procState = uidStats.mProcessState; if (wc != null) { FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), getPowerManagerWakeLockLevel(type), name, - FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); + FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState); } else { FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name, - FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE); + FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE, procState); } } } @@ -4796,16 +4798,18 @@ public class BatteryStatsImpl extends BatteryStats { requestWakelockCpuUpdate(); } - getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs) - .noteStopWakeLocked(pid, name, type, elapsedRealtimeMs); + Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs); + uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs); + + int procState = uidStats.mProcessState; if (wc != null) { FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), getPowerManagerWakeLockLevel(type), name, - FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); + FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState); } else { FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, mapIsolatedUid(uid), null, getPowerManagerWakeLockLevel(type), name, - FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE); + FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE, procState); } if (mappedUid != uid) { diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java index eb6d28e76ff8..73ab7822ac39 100644 --- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java @@ -403,10 +403,12 @@ public class CpuWakeupStats { * This class stores recent unattributed activity history per subsystem. * The activity is stored as a mapping of subsystem to timestamp to uid to procstate. */ - private static final class WakingActivityHistory { + @VisibleForTesting + static final class WakingActivityHistory { private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10); - private SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = + @VisibleForTesting + final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>(); void recordActivity(int subsystem, long elapsedRealtime, SparseIntArray uidProcStates) { @@ -430,7 +432,6 @@ public class CpuWakeupStats { uidsToBlame.put(uid, uidProcStates.valueAt(i)); } } - wakingActivity.put(elapsedRealtime, uidsToBlame); } // Limit activity history per subsystem to the last WAKING_ACTIVITY_RETENTION_MS. // Note that the last activity is always present, even if it occurred before diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 84d3a216ac91..46aa38714b1c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -514,6 +514,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (RemoteException e) { // if this fails we don't really care; the setting app may just // have crashed and that sort of thing is a fact of life. + Slog.w(TAG, "onWallpaperChanged threw an exception", e); } } } @@ -524,7 +525,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { cb.onWallpaperChanged(); } catch (RemoteException e) { - // Oh well it went away; no big deal + Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e); } } } @@ -620,6 +621,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } catch (RemoteException e) { // Callback is gone, it's not necessary to unregister it since // RemoteCallbackList#getBroadcastItem will take care of it. + Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e); } } @@ -628,7 +630,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId); } catch (RemoteException e) { - // Oh well it went away; no big deal + Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e); } } } @@ -965,7 +967,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub connection.mService.detach(mToken); } } catch (RemoteException e) { - Slog.w(TAG, "connection.mService.destroy() threw a RemoteException"); + Slog.w(TAG, "connection.mService.destroy() threw a RemoteException", e); } mEngine = null; } @@ -1117,7 +1119,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { cb.onColorsChanged(area, colors); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to notify local color callbacks", e); } }; synchronized (mLock) { @@ -1316,7 +1318,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { mReply.sendResult(null); } catch (RemoteException e) { - Slog.d(TAG, "failed to send callback!", e); + Slog.d(TAG, "Failed to send callback!", e); } finally { Binder.restoreCallingIdentity(ident); } @@ -1583,7 +1585,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mShuttingDown = false; mImageWallpaper = ComponentName.unflattenFromString( context.getResources().getString(R.string.image_wallpaper_component)); - mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context); + mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context); mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mIPackageManager = AppGlobals.getPackageManager(); @@ -1909,7 +1911,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { si = mIPackageManager.getServiceInfo(cname, PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId); - } catch (RemoteException ignored) { + } catch (RemoteException e) { + Slog.w(TAG, "Failure starting previous wallpaper; clearing", e); } if (mIsLockscreenLiveWallpaperEnabled) { @@ -1918,7 +1921,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (si == null) { - Slog.w(TAG, "Failure starting previous wallpaper; clearing"); clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, reply); } else { Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); @@ -1942,8 +1944,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) { if (serviceInfo == null) { - Slog.w(TAG, "Failure starting previous wallpaper; clearing"); - if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) { clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null); clearWallpaperLocked(false, FLAG_LOCK, wallpaper.userId, reply); @@ -2042,7 +2042,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { cb.onWallpaperChanged(); } catch (RemoteException e) { - // Oh well it went away; no big deal + Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e); } } saveSettingsLocked(userId); @@ -2074,6 +2074,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { reply.sendResult(null); } catch (RemoteException e1) { + Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1); } } } finally { @@ -2168,6 +2169,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { engine.setDesiredSize(width, height); } catch (RemoteException e) { + Slog.w(TAG, "Failed to set desired size", e); } notifyCallbacksLocked(wallpaper); } else if (wallpaper.connection.mService != null && connector != null) { @@ -2263,6 +2265,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { engine.setDisplayPadding(padding); } catch (RemoteException e) { + Slog.w(TAG, "Failed to set display padding", e); } notifyCallbacksLocked(wallpaper); } else if (wallpaper.connection.mService != null && connector != null) { @@ -2498,7 +2501,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { engine.setInAmbientMode(inAmbientMode, animationDuration); } catch (RemoteException e) { - // Cannot talk to wallpaper engine. + Slog.w(TAG, "Failed to set ambient mode", e); } } } @@ -2532,7 +2535,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub displayConnector.mEngine.dispatchWallpaperCommand( WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e); } } }); @@ -2571,7 +2574,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1, extras); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e); } } }); @@ -2611,7 +2614,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { engine.onScreenTurnedOn(); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to notify that the screen turned on", e); } } } @@ -2652,7 +2655,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { engine.onScreenTurningOn(); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to notify that the screen is turning on", e); } } } @@ -2690,7 +2693,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY, -1, -1, -1, new Bundle()); } catch (RemoteException e) { - e.printStackTrace(); + Slog.w(TAG, "Failed to notify that the keyguard is going away", e); } } }); @@ -2853,10 +2856,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { connector.mEngine.applyDimming(maxDimAmount); } catch (RemoteException e) { - Slog.w(TAG, - "Can't apply dimming on wallpaper display " - + "connector", - e); + Slog.w(TAG, "Can't apply dimming on wallpaper display " + + "connector", e); } } }); @@ -3573,6 +3574,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { wallpaper.connection.mReply.sendResult(null); } catch (RemoteException e) { + Slog.w(TAG, "Error sending reply to wallpaper before disconnect", e); } wallpaper.connection.mReply = null; } @@ -3640,6 +3642,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // The RemoteCallbackList will take care of removing // the dead object for us. + Slog.w(TAG, "Failed to notify callbacks about wallpaper changes", e); } } wallpaper.callbacks.finishBroadcast(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 3f4a775bc37a..7926216fe15d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -178,6 +178,9 @@ public abstract class ActivityTaskManagerInternal { /** * Returns the top activity from each of the currently visible root tasks, and the related task * id. The first entry will be the focused activity. + * + * <p>NOTE: If the top activity is in the split screen, the other activities in the same split + * screen will also be returned. */ public abstract List<ActivityAssistInfo> getTopVisibleActivities(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 57812c1d604c..fa5da306d5f5 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1884,7 +1884,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Returns {@code true} if the IME is possible to show on the launching activity. */ boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) { - final WindowState win = r.findMainWindow(); + final WindowState win = r.findMainWindow(false /* exclude starting window */); if (win == null) { return false; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 5149985f8ff9..0074ebd579cf 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1746,9 +1746,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> /** * @return a list of pairs, containing activities and their task id which are the top ones in * each visible root task. The first entry will be the focused activity. + * + * <p>NOTE: If the top activity is in the split screen, the other activities in the same split + * screen will also be returned. */ List<ActivityAssistInfo> getTopVisibleActivities() { final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>(); + final ArrayList<ActivityAssistInfo> activityAssistInfos = new ArrayList<>(); final Task topFocusedRootTask = getTopDisplayFocusedRootTask(); // Traverse all displays. forAllRootTasks(rootTask -> { @@ -1756,11 +1760,21 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (rootTask.shouldBeVisible(null /* starting */)) { final ActivityRecord top = rootTask.getTopNonFinishingActivity(); if (top != null) { - ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top); + activityAssistInfos.clear(); + activityAssistInfos.add(new ActivityAssistInfo(top)); + // Check if the activity on the split screen. + final Task adjacentTask = top.getTask().getAdjacentTask(); + if (adjacentTask != null) { + final ActivityRecord adjacentActivityRecord = + adjacentTask.getTopNonFinishingActivity(); + if (adjacentActivityRecord != null) { + activityAssistInfos.add(new ActivityAssistInfo(adjacentActivityRecord)); + } + } if (rootTask == topFocusedRootTask) { - topVisibleActivities.add(0, visibleActivity); + topVisibleActivities.addAll(0, activityAssistInfos); } else { - topVisibleActivities.add(visibleActivity); + topVisibleActivities.addAll(activityAssistInfos); } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 931c2580fa3f..b7c29bf071d8 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5170,6 +5170,12 @@ class Task extends TaskFragment { // task. r.setVisibility(true); ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS); + // If launching behind, the app will start regardless of what's above it, so mark it + // as unknown even before prior `pause`. This also prevents a race between set-ready + // and activityPause. Launch-behind is basically only used for dream now. + if (!r.isVisibleRequested()) { + r.notifyUnknownVisibilityLaunchedForKeyguardTransition(); + } // Go ahead to execute app transition for this activity since the app transition // will not be triggered through the resume channel. mDisplayContent.executeAppTransition(); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 829a33d4bfb7..be5f141b3762 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1416,6 +1416,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void onAppTransitionDone() { + if (mSurfaceFreezer.hasLeash()) { + mSurfaceFreezer.unfreeze(getSyncTransaction()); + } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); wc.onAppTransitionDone(); @@ -3990,6 +3993,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } // Otherwise this is the "root" of a synced subtree, so continue on to preparation. } + if (oldParent != null && newParent != null && !shouldUpdateSyncOnReparent()) { + return; + } // This container's situation has changed so we need to restart its sync. // We cannot reset the sync without a chance of a deadlock since it will request a new @@ -4004,6 +4010,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< prepareSync(); } + /** Returns {@code true} if {@link #mSyncState} needs to be updated when reparenting. */ + protected boolean shouldUpdateSyncOnReparent() { + return true; + } + void registerWindowContainerListener(WindowContainerListener listener) { registerWindowContainerListener(listener, true /* shouldPropConfig */); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index f253fb0c7271..1a2b57cc7d61 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2675,7 +2675,7 @@ public class WindowManagerService extends IWindowManager.Stub void finishDrawingWindow(Session session, IWindow client, @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) { if (postDrawTransaction != null) { - postDrawTransaction.sanitize(); + postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid()); } final long origId = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 032f08a92abb..bab7a78a7286 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5635,6 +5635,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override + protected boolean shouldUpdateSyncOnReparent() { + // Keep the sync state in case the client is drawing for the latest conifguration or the + // configuration is not changed after reparenting. This avoids a redundant redraw request. + return mSyncState != SYNC_STATE_NONE && !mLastConfigReportedToClient; + } + + @Override boolean prepareSync() { if (!mDrawHandlers.isEmpty()) { Slog.w(TAG, "prepareSync with mDrawHandlers, " + this + ", " + Debug.getCallers(8)); diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index 04ecd6ebd2d1..e3d4c224f1fd 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -19,6 +19,7 @@ package com.android.server.credentials; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.credentials.ClearCredentialStateException; import android.credentials.ClearCredentialStateRequest; import android.credentials.CredentialProviderInfo; import android.credentials.IClearCredentialStateCallback; @@ -29,6 +30,8 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; + import java.util.ArrayList; import java.util.Set; @@ -92,9 +95,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta public void onFinalResponseReceived( ComponentName componentName, Void response) { - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } respondToClientWithResponseAndFinish(null); } @@ -141,8 +147,9 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta return; } } - // TODO: Replace with properly defined error type - respondToClientWithErrorAndFinish("UNKNOWN", "All providers failed"); + String exception = ClearCredentialStateException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "All providers failed"); } @Override diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 0af56470965c..2d6e74f0ccf1 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -35,6 +35,7 @@ import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -131,9 +132,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Nullable CreateCredentialResponse response) { Slog.i(TAG, "Final credential received from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( - componentName.flattenToString()).mProviderSessionMetric - .getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -141,7 +145,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response"); } } @@ -154,18 +160,21 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = CreateCredentialException.TYPE_USER_CANCELED; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = CreateCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } @@ -181,7 +190,9 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, + String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No create options available."); } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index b3812c9138de..25561ed25f20 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -33,6 +33,7 @@ import android.os.IBinder; 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; @@ -71,6 +72,8 @@ public class CredentialManagerUi { }; private void handleUiResult(int resultCode, Bundle resultData) { + Log.i("reemademo", "handleUiResult with resultCOde: " + resultCode); + switch (resultCode) { case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION: mStatus = UiStatus.IN_PROGRESS; @@ -83,10 +86,14 @@ public class CredentialManagerUi { } 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); break; @@ -95,7 +102,7 @@ public class CredentialManagerUi { mCallbacks.onUiSelectorInvocationFailure(); break; default: - Slog.i(TAG, "Unknown error code returned from the UI"); + 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 e9fa88328691..9eb3b3b5b7cd 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -32,6 +32,7 @@ import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.util.Slog; +import com.android.server.credentials.metrics.ProviderSessionMetric; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; @@ -106,8 +107,10 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); + String exception = GetCredentialException.TYPE_UNKNOWN; + mRequestSessionMetric.collectFrameworkException(exception); respondToClientWithErrorAndFinish( - GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); + exception, "Unable to instantiate selector"); } } @@ -128,9 +131,12 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Nullable GetCredentialResponse response) { Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); - mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( - mProviders.get(componentName.flattenToString()) - .mProviderSessionMetric.getCandidatePhasePerProviderMetric()); + if (mProviders.get(componentName.flattenToString()) != null) { + ProviderSessionMetric providerSessionMetric = + mProviders.get(componentName.flattenToString()).mProviderSessionMetric; + mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric + .getCandidatePhasePerProviderMetric()); + } if (response != null) { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode()); @@ -138,7 +144,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, } else { mRequestSessionMetric.collectChosenProviderStatus( ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode()); - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "Invalid response from provider"); } } @@ -152,18 +160,21 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onUiCancellation(boolean isUserCancellation) { - if (isUserCancellation) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED, - "User cancelled the selector"); - } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED, - "The UI was interrupted - please try again."); + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + String message = "User cancelled the selector"; + if (!isUserCancellation) { + exception = GetCredentialException.TYPE_INTERRUPTED; + message = "The UI was interrupted - please try again."; } + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, message); } @Override public void onUiSelectorInvocationFailure() { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available."); } @@ -187,7 +198,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, Slog.i(TAG, "Provider status changed - ui invocation is needed"); getProviderDataAndInitiateUi(); } else { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } @@ -208,7 +221,9 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, // Respond to client if all auth entries are empty and nothing else to show on the UI if (providerDataContainsEmptyAuthEntriesOnly()) { - respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, + String exception = GetCredentialException.TYPE_NO_CREDENTIAL; + mRequestSessionMetric.collectFrameworkException(exception); + respondToClientWithErrorAndFinish(exception, "No credentials available"); } } diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java index e4c6b3a10dd8..f9c44a94f89b 100644 --- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java +++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java @@ -24,12 +24,14 @@ import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; +import com.android.server.credentials.metrics.CandidateAggregateMetric; import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric; import com.android.server.credentials.metrics.CandidatePhaseMetric; import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric; import com.android.server.credentials.metrics.EntryEnum; import com.android.server.credentials.metrics.InitialPhaseMetric; +import java.security.SecureRandom; import java.util.List; import java.util.Map; @@ -48,12 +50,15 @@ public class MetricUtilities { public static final String DEFAULT_STRING = ""; public static final int[] DEFAULT_REPEATED_INT_32 = new int[0]; public static final String[] DEFAULT_REPEATED_STR = new String[0]; + public static final boolean[] DEFAULT_REPEATED_BOOL = new boolean[0]; // Used for single count metric emits, such as singular amounts of various types public static final int UNIT = 1; // Used for zero count metric emits, such as zero amounts of various types public static final int ZERO = 0; // The number of characters at the end of the string to use as a key - public static final int DELTA_CUT = 20; + public static final int DELTA_RESPONSES_CUT = 20; + // The cut for exception strings from the end - used to keep metrics small + public static final int DELTA_EXCEPTION_CUT = 30; /** * This retrieves the uid of any package name, given a context and a component name for the @@ -76,6 +81,15 @@ public class MetricUtilities { } /** + * Used to help generate random sequences for local sessions, in the time-scale of credential + * manager flows. + * @return a high entropy int useful to use in reasonable time-frame sessions. + */ + public static int getHighlyUniqueInteger() { + return new SecureRandom().nextInt(); + } + + /** * Given any two timestamps in nanoseconds, this gets the difference and converts to * milliseconds. Assumes the difference is not larger than the maximum int size. * @@ -127,7 +141,7 @@ public class MetricUtilities { index++; } FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED, - /* session_id */ finalPhaseMetric.getSessionId(), + /* session_id */ finalPhaseMetric.getSessionIdProvider(), /* sequence_num */ emitSequenceId, /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), /* chosen_provider_uid */ finalPhaseMetric.getChosenUid(), @@ -165,8 +179,9 @@ public class MetricUtilities { finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), /* per_classtype_counts */ finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), - /* framework_exception_unique_classtypes */ - DEFAULT_STRING + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* primary_indicated */ false ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during final provider uid emit: " + e); @@ -210,7 +225,7 @@ public class MetricUtilities { CandidatePhaseMetric metric = session.mProviderSessionMetric .getCandidatePhasePerProviderMetric(); if (sessionId == -1) { - sessionId = metric.getSessionId(); + sessionId = metric.getSessionIdProvider(); } if (!queryReturned) { queryReturned = metric.isQueryReturned(); @@ -268,7 +283,9 @@ public class MetricUtilities { /* per_classtype_counts */ initialPhaseMetric.getUniqueRequestCounts(), /* api_name */ - initialPhaseMetric.getApiName() + initialPhaseMetric.getApiName(), + /* primary_candidates_indicated */ + DEFAULT_REPEATED_BOOL ); } catch (Exception e) { Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e); @@ -312,7 +329,7 @@ public class MetricUtilities { FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE_REPORTED, /* api_name */ initialPhaseMetric.getApiName(), /* caller_uid */ initialPhaseMetric.getCallerUid(), - /* session_id */ initialPhaseMetric.getSessionId(), + /* session_id */ initialPhaseMetric.getSessionIdCaller(), /* sequence_num */ sequenceNum, /* initial_timestamp_reference_nanoseconds */ initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(), @@ -329,4 +346,129 @@ public class MetricUtilities { Slog.w(TAG, "Unexpected error during initial metric emit: " + e); } } + + /** + * A logging utility focused on track 1, where the calling app is known. This captures all + * aggregate information for the candidate phase. + * + * @param candidateAggregateMetric the aggregate candidate metric information collected + * @param sequenceNum the sequence number for this api call session emit + */ + public static void logApiCalledAggregateCandidate( + CandidateAggregateMetric candidateAggregateMetric, + int sequenceNum) { + try { + if (!LOG_FLAG) { + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED, + /*session_id*/ candidateAggregateMetric.getSessionIdProvider(), + /*sequence_num*/ sequenceNum, + /*query_returned*/ candidateAggregateMetric.isQueryReturned(), + /*num_providers*/ candidateAggregateMetric.getNumProviders(), + /*min_query_start_timestamp_microseconds*/ + DEFAULT_INT_32, + /*max_query_end_timestamp_microseconds*/ + DEFAULT_INT_32, + /*query_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*query_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*query_total_candidate_failure*/ + DEFAULT_INT_32, + /*query_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*query_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_response_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_unique_entries*/ + DEFAULT_REPEATED_INT_32, + /*auth_per_entry_counts*/ + DEFAULT_REPEATED_INT_32, + /*auth_total_candidate_failure*/ + DEFAULT_INT_32, + /*auth_framework_exception_unique_classtypes*/ + DEFAULT_REPEATED_STR, + /*auth_per_exception_classtype_counts*/ + DEFAULT_REPEATED_INT_32, + /*num_auth_clicks*/ + DEFAULT_INT_32, + /*auth_returned*/ false + ); + } + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** + * A logging utility used primarily for the final phase of the current metric setup for track 1. + * + * @param finalPhaseMetric the coalesced data of the chosen provider + * @param browsingPhaseMetrics the coalesced data of the browsing phase + * @param apiStatus the final status of this particular api call + * @param emitSequenceId an emitted sequence id for the current session + */ + public static void logApiCalledNoUidFinal(ChosenProviderFinalPhaseMetric finalPhaseMetric, + List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus, + int emitSequenceId) { + try { + if (!LOG_FLAG) { + return; + } + int browsedSize = browsingPhaseMetrics.size(); + int[] browsedClickedEntries = new int[browsedSize]; + int[] browsedProviderUid = new int[browsedSize]; + int index = 0; + for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) { + browsedClickedEntries[index] = metric.getEntryEnum(); + browsedProviderUid[index] = metric.getProviderUid(); + index++; + } + FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED, + /* session_id */ finalPhaseMetric.getSessionIdCaller(), + /* sequence_num */ emitSequenceId, + /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(), + /* chosen_provider_query_start_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryStartTimeNanoseconds()), + /* chosen_provider_query_end_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getQueryEndTimeNanoseconds()), + /* chosen_provider_ui_invoked_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallStartTimeNanoseconds()), + /* chosen_provider_ui_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getUiCallEndTimeNanoseconds()), + /* chosen_provider_finished_timestamp_microseconds */ + finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric + .getFinalFinishTimeNanoseconds()), + /* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(), + /* chosen_provider_has_exception */ finalPhaseMetric.isHasException(), + /* unique_entries */ + finalPhaseMetric.getResponseCollective().getUniqueEntries(), + /* per_entry_counts */ + finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(), + /* unique_response_classtypes */ + finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(), + /* per_classtype_counts */ + finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(), + /* framework_exception_unique_classtype */ + finalPhaseMetric.getFrameworkException(), + /* clicked_entries */ browsedClickedEntries, + /* provider_of_clicked_entry */ browsedProviderUid, + /* api_status */ apiStatus, + /* primary_indicated */ false + ); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during 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 409806a21679..25f20caee16d 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -193,11 +193,11 @@ public final class ProviderCreateSession extends ProviderSession< mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(), response.getRemoteCreateEntry()); if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); 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 64438e338e5b..51af25b58992 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -433,6 +433,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential BeginGetCredentialResponse response = PendingIntentResultHandler .extractResponseContent(providerPendingIntentResponse .getResultData()); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true); if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) { addToInitialRemoteResponse(response, /*isInitialResponse=*/ false); // Additional content received is in the form of new response content. @@ -470,12 +471,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential addToInitialRemoteResponse(response, /*isInitialResponse=*/true); // Log the data. if (mProviderResponseDataHandler.isEmptyResponse(response)) { - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); return; } - mProviderSessionMetric.collectCandidateEntryMetrics(response); + mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false); 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 27b78a4b7b15..068ca7928117 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -71,7 +71,7 @@ public abstract class ProviderSession<T, R> @NonNull protected Boolean mProviderResponseSet = false; @NonNull - protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric(); + protected final ProviderSessionMetric mProviderSessionMetric; @NonNull private int mProviderSessionUid; @@ -114,6 +114,13 @@ public abstract class ProviderSession<T, R> } /** + * Gives access to the objects metric collectors. + */ + public ProviderSessionMetric getProviderSessionMetric() { + return this.mProviderSessionMetric; + } + + /** * Interface to be implemented by any class that wishes to get a callback when a particular * provider session's status changes. Typically, implemented by the {@link RequestSession} * class. @@ -147,6 +154,8 @@ public abstract class ProviderSession<T, R> mComponentName = componentName; mRemoteCredentialService = remoteCredentialService; mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName); + mProviderSessionMetric = new ProviderSessionMetric( + ((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo()); } /** Provider status at various states of the provider session. */ @@ -206,10 +215,10 @@ public abstract class ProviderSession<T, R> CredentialsSource source) { setStatus(status); mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status), - isCompletionStatus(status), mProviderSessionUid); + isCompletionStatus(status), mProviderSessionUid, + source == CredentialsSource.AUTH_ENTRY); mCallbacks.onProviderStatusChanged(status, mComponentName, source); } - /** Common method that transfers metrics from the init phase to candidates */ protected void startCandidateMetrics() { mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric( diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 7caa921eacda..a41b5713ee14 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -75,6 +75,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final Handler mHandler; @UserIdInt protected final int mUserId; + + protected final int mUniqueSessionInteger; private final int mCallingUid; @NonNull protected final CallingAppInfo mClientAppInfo; @@ -82,7 +84,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final CancellationSignal mCancellationSignal; protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>(); - protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric(); + protected final RequestSessionMetric mRequestSessionMetric; protected final String mHybridService; protected final Object mLock; @@ -132,7 +134,10 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mUserId, this, mEnabledProviders); mHybridService = context.getResources().getString( R.string.config_defaultCredentialManagerHybridService); - mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId, + mUniqueSessionInteger = MetricUtilities.getHighlyUniqueInteger(); + mRequestSessionMetric = new RequestSessionMetric(mUniqueSessionInteger, + MetricUtilities.getHighlyUniqueInteger()); + mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); setCancellationListener(); } diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java new file mode 100644 index 000000000000..51e86d51acdf --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java @@ -0,0 +1,36 @@ +/* + * 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.server.credentials.metrics; + +/** + * Encapsulates an authentication entry click atom, as a part of track 2. + * Contains information about what was collected from the authentication entry output. + */ +public class BrowsedAuthenticationMetric { + // The session id of this provider known flow related metric + private final int mSessionIdProvider; + // TODO(b/271135048) - Match the atom and provide a clean per provider session metric + // encapsulation. + + public BrowsedAuthenticationMetric(int sessionIdProvider) { + mSessionIdProvider = sessionIdProvider; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java new file mode 100644 index 000000000000..08e75837a274 --- /dev/null +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java @@ -0,0 +1,75 @@ +/* + * 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.server.credentials.metrics; + +import com.android.server.credentials.ProviderSession; + +import java.util.Map; + +/** + * This will generate most of its data via using the information of {@link CandidatePhaseMetric} + * across all the providers. This belongs to the metric flow where the calling app is known. + */ +public class CandidateAggregateMetric { + + private static final String TAG = "CandidateProviderMetric"; + // The session id of this provider metric + private final int mSessionIdProvider; + // Indicates if this provider returned from the query phase, default false + private boolean mQueryReturned = false; + // Indicates the total number of providers this aggregate captures information for, default 0 + private int mNumProviders = 0; + // Indicates the total number of authentication entries that were tapped in aggregate, default 0 + private int mNumAuthEntriesTapped = 0; + + public CandidateAggregateMetric(int sessionIdTrackOne) { + mSessionIdProvider = sessionIdTrackOne; + } + + public int getSessionIdProvider() { + return mSessionIdProvider; + } + + /** + * 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) { + // TODO(b/271135048) : Complete this method + mNumProviders = providers.size(); + var providerSessions = providers.values(); + for (var session : providerSessions) { + var metric = session.getProviderSessionMetric(); + mQueryReturned = mQueryReturned || metric + .mCandidatePhasePerProviderMetric.isQueryReturned(); + } + } + + public int getNumProviders() { + return mNumProviders; + } + + public boolean isQueryReturned() { + return mQueryReturned; + } + + public int getNumAuthEntriesTapped() { + return mNumAuthEntriesTapped; + } +} diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java index 07af6549411e..6b74252dec19 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java @@ -27,23 +27,11 @@ package com.android.server.credentials.metrics; * though collection will begin in the candidate phase when the user begins browsing options. */ public class CandidateBrowsingPhaseMetric { - // The session id associated with the API Call this candidate provider is a part of, default -1 - private int mSessionId = -1; // The EntryEnum that was pressed, defaults to -1 private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode(); // The provider associated with the press, defaults to -1 private int mProviderUid = -1; - /* -- The session ID -- */ - - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; - } - /* -- The Entry of this tap -- */ public void setEntryEnum(int entryEnum) { diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java index 3ea9b1ce86f8..d9bf4a134adb 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java @@ -32,8 +32,8 @@ import java.util.Map; public class CandidatePhaseMetric { private static final String TAG = "CandidateProviderMetric"; - // The session id of this provider, default set to -1 - private int mSessionId = -1; + // The session id of this provider metric + private final int mSessionIdProvider; // Indicates if this provider returned from the query phase, default false private boolean mQueryReturned = false; @@ -53,13 +53,15 @@ public class CandidatePhaseMetric { private int mProviderQueryStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates the framework only exception belonging to this provider private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public CandidatePhaseMetric() { + public CandidatePhaseMetric(int sessionIdTrackTwo) { + mSessionIdProvider = sessionIdTrackTwo; } /* ---------- Latencies ---------- */ @@ -141,12 +143,8 @@ public class CandidatePhaseMetric { /* -------------- Session Id ---------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* -------------- Query Returned Status ---------------- */ diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java index 93a82906aa50..e8af86012aaf 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java @@ -32,8 +32,12 @@ import java.util.Map; */ public class ChosenProviderFinalPhaseMetric { private static final String TAG = "ChosenFinalPhaseMetric"; - // The session id associated with this API call, used to unite split emits - private int mSessionId = -1; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the calling app + private final int mSessionIdCaller; + // The session id associated with this API call, used to unite split emits, for the flow + // where we know the provider apps + private final int mSessionIdProvider; // Reveals if the UI was returned, false by default private boolean mUiReturned = false; private int mChosenUid = -1; @@ -66,13 +70,17 @@ public class ChosenProviderFinalPhaseMetric { private int mChosenProviderStatus = -1; // Indicates if an exception was thrown by this provider, false by default private boolean mHasException = false; + // Indicates a framework only exception that occurs in the final phase of the flow + private String mFrameworkException = ""; // Stores the response credential information, as well as the response entry information which // by default, contains empty info private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of()); - public ChosenProviderFinalPhaseMetric() { + public ChosenProviderFinalPhaseMetric(int sessionIdCaller, int sessionIdProvider) { + mSessionIdCaller = sessionIdCaller; + mSessionIdProvider = sessionIdProvider; } /* ------------------- UID ------------------- */ @@ -235,12 +243,8 @@ public class ChosenProviderFinalPhaseMetric { /* ----------- Session ID -------------- */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdProvider() { + return mSessionIdProvider; } /* ----------- UI Returned Successfully -------------- */ @@ -272,4 +276,20 @@ public class ChosenProviderFinalPhaseMetric { public ResponseCollective getResponseCollective() { return mResponseCollective; } + + /* -------------- Framework Exception ---------------- */ + + public void setFrameworkException(String frameworkException) { + mFrameworkException = frameworkException; + } + + public String getFrameworkException() { + return mFrameworkException; + } + + /* -------------- Session ID for Track One (Known Calling App) ---------------- */ + + public int getSessionIdCaller() { + return mSessionIdCaller; + } } diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java index 060e56ce965b..8e965e3e5ba5 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java @@ -32,8 +32,8 @@ public class InitialPhaseMetric { private int mApiName = ApiName.UNKNOWN.getMetricCode(); // The caller uid of the calling application, default to -1 private int mCallerUid = -1; - // The session id to unite multiple atom emits, default to -1 - private int mSessionId = -1; + // The session id to unite multiple atom emits + private final int mSessionIdCaller; // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a // reference point. @@ -50,7 +50,8 @@ public class InitialPhaseMetric { private Map<String, Integer> mRequestCounts = new LinkedHashMap<>(); - public InitialPhaseMetric() { + public InitialPhaseMetric(int sessionIdTrackOne) { + mSessionIdCaller = sessionIdTrackOne; } /* ---------- Latencies ---------- */ @@ -105,12 +106,8 @@ public class InitialPhaseMetric { /* ------ SessionId ------ */ - public void setSessionId(int sessionId) { - mSessionId = sessionId; - } - - public int getSessionId() { - return mSessionId; + public int getSessionIdCaller() { + return mSessionIdCaller; } /* ------ Count Request Class Types ------ */ 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 f011b554fe53..47db8f59ff35 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java @@ -16,7 +16,7 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; import android.annotation.NonNull; @@ -44,10 +44,17 @@ public class ProviderSessionMetric { // Specific candidate provider metric for the provider this session handles @NonNull - protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric = - new CandidatePhaseMetric(); + protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric; - public ProviderSessionMetric() {} + // IFF there was an authentication entry clicked, this stores all required information for + // that event. This is for the 'get' flow. + @NonNull + protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric; + + public ProviderSessionMetric(int sessionIdTrackTwo) { + mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo); + mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo); + } /** * Retrieve the candidate provider phase metric and the data it contains. @@ -56,6 +63,7 @@ public class ProviderSessionMetric { return mCandidatePhasePerProviderMetric; } + /** * This collects for ProviderSessions, with respect to the candidate providers, whether * an exception occurred in the candidate call. @@ -78,6 +86,13 @@ public class ProviderSessionMetric { } } + private void collectAuthEntryUpdate(boolean isFailureStatus, + boolean isCompletionStatus, int providerSessionUid) { + // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric + // Collect the final timestamps (and start timestamp), status, exceptions and the provider + // uid. This occurs typically *after* the collection is complete. + } + /** * Used to collect metrics at the update stage when a candidate provider gives back an update. * @@ -86,8 +101,12 @@ public class ProviderSessionMetric { * @param providerSessionUid the uid of the provider */ public void collectCandidateMetricUpdate(boolean isFailureStatus, - boolean isCompletionStatus, int providerSessionUid) { + boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry) { try { + if (isAuthEntry) { + collectAuthEntryUpdate(isFailureStatus, isCompletionStatus, providerSessionUid); + return; + } mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid); mCandidatePhasePerProviderMetric .setQueryFinishTimeNanoseconds(System.nanoTime()); @@ -119,7 +138,6 @@ public class ProviderSessionMetric { */ public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) { try { - mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId()); mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds( initMetric.getCredentialServiceStartedTimeNanoseconds()); mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime()); @@ -133,13 +151,14 @@ public class ProviderSessionMetric { * purposes. * * @param response contains entries and data from the candidate provider responses + * @param isAuthEntry indicates if this is an auth entry collection or not * @param <R> the response type associated with the API flow in progress */ - public <R> void collectCandidateEntryMetrics(R response) { + public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry) { try { if (response instanceof BeginGetCredentialResponse) { beginGetCredentialResponseCollectionCandidateEntryMetrics( - (BeginGetCredentialResponse) response); + (BeginGetCredentialResponse) response, isAuthEntry); } else if (response instanceof BeginCreateCredentialResponse) { beginCreateCredentialResponseCollectionCandidateEntryMetrics( (BeginCreateCredentialResponse) response); @@ -170,7 +189,7 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); entries.forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT); responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1); }); @@ -198,7 +217,7 @@ public class ProviderSessionMetric { } private void beginGetCredentialResponseCollectionCandidateEntryMetrics( - BeginGetCredentialResponse response) { + BeginGetCredentialResponse response, boolean isAuthEntry) { Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>(); Map<String, Integer> responseCounts = new LinkedHashMap<>(); int numCredEntries = response.getCredentialEntries().size(); @@ -212,11 +231,16 @@ public class ProviderSessionMetric { entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries); response.getCredentialEntries().forEach(entry -> { - String entryKey = generateMetricKey(entry.getType(), DELTA_CUT); + 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); + + if (!isAuthEntry) { + mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective); + } else { + // TODO(b/immediately) - Add the auth entry get logic + } } } 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 4624e0b3701a..03ffe23f9886 100644 --- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java +++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java @@ -16,14 +16,16 @@ package com.android.server.credentials.metrics; -import static com.android.server.credentials.MetricUtilities.DELTA_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT; +import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT; import static com.android.server.credentials.MetricUtilities.generateMetricKey; 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 android.annotation.NonNull; import android.credentials.GetCredentialRequest; import android.credentials.ui.UserSelectionDialogResult; -import android.os.IBinder; import android.util.Slog; import com.android.server.credentials.ProviderSession; @@ -47,13 +49,24 @@ public class RequestSessionMetric { // As emits occur in sequential order, increment this counter and utilize protected int mSequenceCounter = 0; - protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric(); + protected final InitialPhaseMetric mInitialPhaseMetric; protected final ChosenProviderFinalPhaseMetric - mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric(); + mChosenProviderFinalPhaseMetric; // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4) protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>(); + // Specific aggregate candidate provider metric for the provider this session handles + @NonNull + protected final CandidateAggregateMetric mCandidateAggregateMetric; + // Since track two is shared, this allows provider sessions to capture a metric-specific + // session token for the flow where the provider is known + private final int mSessionIdTrackTwo; - public RequestSessionMetric() { + public RequestSessionMetric(int sessionIdTrackOne, int sessionIdTrackTwo) { + mSessionIdTrackTwo = sessionIdTrackTwo; + mInitialPhaseMetric = new InitialPhaseMetric(sessionIdTrackOne); + mCandidateAggregateMetric = new CandidateAggregateMetric(sessionIdTrackOne); + mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric( + sessionIdTrackOne, sessionIdTrackTwo); } /** @@ -75,18 +88,25 @@ public class RequestSessionMetric { } /** + * @return the aggregate candidate phase metrics associated with the request session + */ + public CandidateAggregateMetric getCandidateAggregateMetric() { + return mCandidateAggregateMetric; + } + + /** * Upon starting the service, this fills the initial phase metric properly. * * @param timestampStarted the timestamp the service begins at - * @param mRequestId the IBinder used to retrieve a unique id * @param mCallingUid the calling process's uid * @param metricCode typically pulled from {@link ApiName} + * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app + * known flow */ - public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId, + public void collectInitialPhaseMetricInfo(long timestampStarted, int mCallingUid, int metricCode) { try { mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted); - mInitialPhaseMetric.setSessionId(mRequestId.hashCode()); mInitialPhaseMetric.setCallerUid(mCallingUid); mInitialPhaseMetric.setApiName(metricCode); } catch (Exception e) { @@ -168,11 +188,9 @@ public class RequestSessionMetric { Map<String, Integer> uniqueRequestCounts = new LinkedHashMap<>(); try { request.getCredentialOptions().forEach(option -> { - String optionKey = generateMetricKey(option.getType(), DELTA_CUT); - if (!uniqueRequestCounts.containsKey(optionKey)) { - uniqueRequestCounts.put(optionKey, 0); - } - uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1); + String optionKey = generateMetricKey(option.getType(), DELTA_RESPONSES_CUT); + uniqueRequestCounts.put(optionKey, uniqueRequestCounts.getOrDefault(optionKey, + 0) + 1); }); } catch (Exception e) { Slog.i(TAG, "Unexpected error during get request metric logging: " + e); @@ -207,7 +225,6 @@ public class RequestSessionMetric { CandidatePhaseMetric selectedProviderPhaseMetric) { try { CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric(); - browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId()); browsingPhaseMetric.setEntryEnum( EntryEnum.getMetricCodeFromString(selection.getEntryKey())); browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid()); @@ -218,7 +235,7 @@ public class RequestSessionMetric { } /** - * Updates the final phase metric with the designated bit + * Updates the final phase metric with the designated bit. * * @param exceptionBitFinalPhase represents if the final phase provider had an exception */ @@ -231,6 +248,21 @@ public class RequestSessionMetric { } /** + * This allows collecting the framework exception string for the final phase metric. + * NOTE that this exception will be cut for space optimizations. + * + * @param exception the framework exception that is being recorded + */ + public void collectFrameworkException(String exception) { + try { + mChosenProviderFinalPhaseMetric.setFrameworkException( + generateMetricKey(exception, DELTA_EXCEPTION_CUT)); + } catch (Exception e) { + Slog.w(TAG, "Unexpected error during metric logging: " + e); + } + } + + /** * Allows encapsulating the overall final phase metric status from the chosen and final * provider. * @@ -260,7 +292,6 @@ public class RequestSessionMetric { */ public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) { try { - mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId()); mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid()); mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds( @@ -284,7 +315,7 @@ public class RequestSessionMetric { * In the final phase, this helps log use cases that were either pure failures or user * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean, * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this. - * Otherwise, the logging will miss required bits + * Otherwise, the logging will miss required bits. * * @param isUserCanceledError a boolean indicating if the error was due to user cancelling */ @@ -318,6 +349,20 @@ public class RequestSessionMetric { } /** + * Handles aggregate candidate phase metric emits in the RequestSession context, after the + * candidate phase completes. + * + * @param providers a map with known providers and their held metric objects + */ + public void logCandidateAggregateMetrics(Map<String, ProviderSession> providers) { + try { + mCandidateAggregateMetric.collectAverages(providers); + } catch (Exception e) { + Slog.i(TAG, "Unexpected error during aggregate candidate logging " + e); + } + } + + /** * Handles the final logging for RequestSession context for the final phase. * * @param apiStatus the final status of the api being called @@ -327,9 +372,15 @@ public class RequestSessionMetric { logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, apiStatus, ++mSequenceCounter); + logApiCalledNoUidFinal(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric, + apiStatus, + ++mSequenceCounter); } catch (Exception e) { Slog.i(TAG, "Unexpected error during final metric emit: " + e); } } + public int getSessionIdTrackTwo() { + return mSessionIdTrackTwo; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index bb3b4386a4de..02c6d6849cca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -447,6 +447,7 @@ import android.util.AtomicFile; import android.util.DebugUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; +import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -1214,7 +1215,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STOPPED, userHandle); if (isManagedProfile(userHandle)) { Slogf.d(LOG_TAG, "Managed profile was stopped"); - updatePersonalAppsSuspension(userHandle, false /* unlocked */); + updatePersonalAppsSuspension(userHandle); } } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle); @@ -1224,8 +1225,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (isManagedProfile(userHandle)) { Slogf.d(LOG_TAG, "Managed profile became unlocked"); - final boolean suspended = - updatePersonalAppsSuspension(userHandle, true /* unlocked */); + final boolean suspended = updatePersonalAppsSuspension(userHandle); triggerPolicyComplianceCheckIfNeeded(userHandle, suspended); } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { @@ -1252,13 +1252,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true); final int userId = getManagedUserId(getMainUserId()); if (userId >= 0) { - updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId)); + updatePersonalAppsSuspension(userId); } } else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) { Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered"); final int userId = getManagedUserId(getMainUserId()); if (userId >= 0) { - updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId)); + updatePersonalAppsSuspension(userId); } else { Slogf.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile"); } @@ -1268,9 +1268,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { notifyIfManagedSubscriptionsAreUnavailable( UserHandle.of(userHandle), /* managedProfileAvailable= */ false); + updatePersonalAppsSuspension(userHandle); } else if (ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)) { notifyIfManagedSubscriptionsAreUnavailable( UserHandle.of(userHandle), /* managedProfileAvailable= */ true); + final boolean suspended = updatePersonalAppsSuspension(userHandle); + triggerPolicyComplianceCheckIfNeeded(userHandle, suspended); } else if (LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) { calculateHasIncompatibleAccounts(); } @@ -3433,7 +3436,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int profileUserHandle = getManagedUserId(userHandle); if (profileUserHandle >= 0) { // Given that the parent user has just started, profile should be locked. - updatePersonalAppsSuspension(profileUserHandle, false /* unlocked */); + updatePersonalAppsSuspension(profileUserHandle); } else { suspendPersonalAppsInternal(userHandle, profileUserHandle, false); } @@ -3611,10 +3614,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isProfileOwnerOfOrganizationOwnedDevice(userId) && getManagedSubscriptionsPolicy().getPolicyType() == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) { - String defaultDialerPackageName = getOemDefaultDialerPackage(); - String defaultSmsPackageName = getOemDefaultSmsPackage(); - updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName, - defaultSmsPackageName); + updateDialerAndSmsManagedShortcutsOverrideCache(); } startOwnerService(userId, "start-user"); @@ -10728,15 +10728,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return UserHandle.USER_NULL; } - private @UserIdInt int getManagedProfileUserId() { - for (UserInfo ui : mUserManagerInternal.getUserInfos()) { - if (ui.isManagedProfile()) { - return ui.id; - } - } - return UserHandle.USER_NULL; - } - /** * This API is cached: invalidate with invalidateBinderCaches(). */ @@ -11599,6 +11590,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { final ActiveAdmin activeAdmin = getParentOfAdminIfRequired( getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); + + if (isManagedProfile(userId)) { + mInjector.binderWithCleanCallingIdentity( + () -> updateDialerAndSmsManagedShortcutsOverrideCache()); + } + if (!Objects.equals(activeAdmin.mSmsPackage, packageName)) { activeAdmin.mSmsPackage = packageName; saveSettingsLocked(caller.getUserId()); @@ -11644,6 +11641,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); // Only save the package when the setting the role succeeded without exception. synchronized (getLockObject()) { + if (isManagedProfile(callerUserId)) { + mInjector.binderWithCleanCallingIdentity( + () -> updateDialerAndSmsManagedShortcutsOverrideCache()); + } + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(callerUserId); if (!Objects.equals(admin.mDialerPackage, packageName)) { admin.mDialerPackage = packageName; @@ -12079,7 +12081,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Objects.requireNonNull(who, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(who); Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller)); + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); if (packageList != null) { for (String pkg : packageList) { @@ -13364,8 +13366,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { PolicyDefinition<Boolean> policyDefinition = PolicyDefinition.getPolicyDefinitionForUserRestriction(key); if (enabledFromThisOwner) { - setLocalUserRestrictionInternal( - admin, key, /* enabled= */ true, affectedUserId); + // 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); + } } else { // Remove any local and global policy that was set by the admin if (!policyDefinition.isLocalOnlyPolicy()) { @@ -15154,7 +15162,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_LOCKTASK_MODE_ENABLED) - .setAdmin(admin.info.getPackageName()) + .setAdmin(admin.info == null ? null : admin.info.getPackageName()) .setBoolean(isEnabled) .setStrings(pkg) .write(); @@ -20787,7 +20795,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.binderWithCleanCallingIdentity(() -> updatePersonalAppsSuspension( - callingUserId, mUserManager.isUserUnlocked(callingUserId))); + callingUserId)); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED) @@ -20821,16 +20829,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks whether personal apps should be suspended according to the policy and applies the * change if needed. - * - * @param unlocked whether the profile is currently running unlocked. */ - private boolean updatePersonalAppsSuspension(int profileUserId, boolean unlocked) { + private boolean updatePersonalAppsSuspension(int profileUserId) { final boolean shouldSuspend; synchronized (getLockObject()) { final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId); if (profileOwner != null) { - final int notificationState = - updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked); + // Profile is considered "off" when it is either not running or is running locked + // or is in quiet mode, i.e. when the admin cannot sync policies or show UI. + boolean profileUserOff = + !mUserManagerInternal.isUserUnlockingOrUnlocked(profileUserId) + || mUserManager.isQuietModeEnabled(UserHandle.of(profileUserId)); + final int notificationState = updateProfileOffDeadlineLocked( + profileUserId, profileOwner, profileUserOff); final boolean suspendedExplicitly = profileOwner.mSuspendPersonalApps; final boolean suspendedByTimeout = profileOwner.mProfileOffDeadline == -1; Slogf.d(LOG_TAG, @@ -20855,16 +20866,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * @return notification state */ private int updateProfileOffDeadlineLocked( - int profileUserId, ActiveAdmin profileOwner, boolean unlocked) { + int profileUserId, ActiveAdmin profileOwner, boolean off) { final long now = mInjector.systemCurrentTimeMillis(); if (profileOwner.mProfileOffDeadline != 0 && now > profileOwner.mProfileOffDeadline) { - Slogf.i(LOG_TAG, "Profile off deadline has been reached, unlocked: " + unlocked); + Slogf.i(LOG_TAG, "Profile off deadline has been reached, off: " + off); if (profileOwner.mProfileOffDeadline != -1) { // Move the deadline far to the past so that it cannot be rolled back by TZ change. profileOwner.mProfileOffDeadline = -1; saveSettingsLocked(profileUserId); } - return unlocked ? PROFILE_OFF_NOTIFICATION_NONE : PROFILE_OFF_NOTIFICATION_SUSPENDED; + return off ? PROFILE_OFF_NOTIFICATION_SUSPENDED : PROFILE_OFF_NOTIFICATION_NONE; } boolean shouldSaveSettings = false; if (profileOwner.mSuspendPersonalApps) { @@ -20881,7 +20892,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { profileOwner.mProfileOffDeadline = 0; shouldSaveSettings = true; } else if (profileOwner.mProfileOffDeadline == 0 - && (profileOwner.mProfileMaximumTimeOffMillis != 0 && !unlocked)) { + && (profileOwner.mProfileMaximumTimeOffMillis != 0 && off)) { // There profile is locked and there is a policy, but the deadline is not set -> set the // deadline. Slogf.i(LOG_TAG, "Profile off deadline is set."); @@ -20895,7 +20906,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final long alarmTime; final int notificationState; - if (unlocked || profileOwner.mProfileOffDeadline == 0) { + if (!off || profileOwner.mProfileOffDeadline == 0) { alarmTime = 0; notificationState = PROFILE_OFF_NOTIFICATION_NONE; } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) { @@ -21169,7 +21180,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } mInjector.binderWithCleanCallingIdentity( - () -> updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked())); + () -> updatePersonalAppsSuspension(userId)); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF) @@ -21814,10 +21825,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final AccountManager accountManager = mContext.createContextAsUser( UserHandle.of(sourceUserId), /* flags= */ 0) .getSystemService(AccountManager.class); - final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account, - null, null /* callback */, null /* handler */); try { - final Bundle result = bundle.getResult(); + final Bundle result = accountManager.removeAccount(account, + null, null /* callback */, null /* handler */).getResult(60, TimeUnit.SECONDS); if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, /* default */ false)) { Slogf.i(LOG_TAG, "Account removed from the primary user."); } else { @@ -22718,7 +22728,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void handleFinancedDeviceKioskRoleChange() { - if (!isPermissionCheckFlagEnabled()) { + if (!isPolicyEngineForFinanceFlagEnabled()) { return; } Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED); @@ -23934,8 +23944,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null"); } - updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName, - defaultSmsPackageName); + updateDialerAndSmsManagedShortcutsOverrideCache(); } catch (RemoteException re) { // shouldn't happen Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re); @@ -23951,17 +23960,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mContext.getString(R.string.config_defaultSms); } - private void updateDialerAndSmsManagedShortcutsOverrideCache( - String defaultDialerPackageName, String defaultSmsPackageName) { + private void updateDialerAndSmsManagedShortcutsOverrideCache() { ArrayMap<String, String> shortcutOverrides = new ArrayMap<>(); + int managedUserId = getManagedUserId(); + List<String> dialerRoleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_DIALER, + UserHandle.of(managedUserId)); + List<String> smsRoleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_SMS, + UserHandle.of(managedUserId)); - if (defaultDialerPackageName != null) { - shortcutOverrides.put(defaultDialerPackageName, defaultDialerPackageName); - } + String dialerPackageToOverride = getOemDefaultDialerPackage(); + String smsPackageToOverride = getOemDefaultSmsPackage(); - if (defaultSmsPackageName != null) { - shortcutOverrides.put(defaultSmsPackageName, defaultSmsPackageName); + // To get the default app, we can get all the role holders and get the first element. + if (dialerPackageToOverride != null) { + shortcutOverrides.put(dialerPackageToOverride, + dialerRoleHolders.isEmpty() ? dialerPackageToOverride + : dialerRoleHolders.get(0)); + } + if (smsPackageToOverride != null) { + shortcutOverrides.put(smsPackageToOverride, + smsRoleHolders.isEmpty() ? smsPackageToOverride : smsRoleHolders.get(0)); } + mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a8a1c0354cf4..af841808992e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -735,7 +735,7 @@ public final class SystemServer implements Dumpable { final String name = args[1]; final Dumpable dumpable = mDumpables.get(name); if (dumpable == null) { - pw.printf("No dummpable named %s\n", name); + pw.printf("No dumpable named %s\n", name); return; } @@ -2644,7 +2644,11 @@ public final class SystemServer implements Dumpable { // AdServicesManagerService (PP API service) t.traceBegin("StartAdServicesManagerService"); - mSystemServiceManager.startService(AD_SERVICES_MANAGER_SERVICE_CLASS); + SystemService adServices = mSystemServiceManager + .startService(AD_SERVICES_MANAGER_SERVICE_CLASS); + if (adServices instanceof Dumpable) { + mDumper.addDumpable((Dumpable) adServices); + } t.traceEnd(); // OnDevicePersonalizationSystemService diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java index 94ee0a871448..91dcd50f176a 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import static org.testng.Assert.expectThrows; @@ -118,6 +119,10 @@ public class BackupManagerServiceRoboTest { mShadowUserManager.addUser(mUserOneId, "mUserOneId", 0); mShadowUserManager.addUser(mUserTwoId, "mUserTwoId", 0); + when(mUserSystemService.getUserId()).thenReturn(UserHandle.USER_SYSTEM); + when(mUserOneService.getUserId()).thenReturn(mUserOneId); + when(mUserTwoService.getUserId()).thenReturn(mUserTwoId); + mShadowContext.grantPermissions(BACKUP); mShadowContext.grantPermissions(INTERACT_ACROSS_USERS_FULL); @@ -1469,9 +1474,9 @@ public class BackupManagerServiceRoboTest { File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); - String[] args = {"1", "2"}; ShadowBinder.setCallingUserHandle(UserHandle.of(UserHandle.USER_SYSTEM)); + String[] args = {"--user", "0"}; backupManagerService.dump(fileDescriptor, printWriter, args); verify(mUserSystemService).dump(fileDescriptor, printWriter, args); @@ -1485,8 +1490,8 @@ public class BackupManagerServiceRoboTest { File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); - String[] args = {"1", "2"}; + String[] args = {"--user", "10"}; backupManagerService.dump(fileDescriptor, printWriter, args); verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args); 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 acfea85d60a2..581fe4acf219 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -393,9 +393,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); - queue.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertFalse(queue.isRunnable()); @@ -420,9 +420,9 @@ public final class BroadcastQueueModernImplTest { List.of(makeMockRegisteredReceiver()), false); enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); - queue.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); final long notCachedRunnableAt = queue.getRunnableAt(); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); final long cachedRunnableAt = queue.getRunnableAt(); assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); @@ -452,13 +452,13 @@ public final class BroadcastQueueModernImplTest { // verify that: // (a) the queue is immediately runnable by existence of a fg-priority broadcast // (b) the next one up is the fg-priority broadcast despite its later enqueue time - queue.setProcessAndUidCached(null, false); + queue.setProcessAndUidState(null, false, false); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); @@ -515,6 +515,28 @@ public final class BroadcastQueueModernImplTest { assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason()); } + @Test + public void testRunnableAt_uidForeground() { + 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()); + + queue.setProcessAndUidState(mProcess, true, false); + assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime); + assertEquals(BroadcastProcessQueue.REASON_FOREGROUND, queue.getRunnableAtReason()); + + 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. @@ -522,7 +544,7 @@ public final class BroadcastQueueModernImplTest { private void doRunnableAt_Cached(BroadcastRecord testRecord, int testRunnableAtReason) { final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.setProcessAndUidCached(null, true); + queue.setProcessAndUidState(null, false, true); final BroadcastRecord lazyRecord = makeBroadcastRecord( new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), @@ -769,7 +791,7 @@ public final class BroadcastQueueModernImplTest { BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.setPrioritizeEarliest(true); + queue.addPrioritizeEarliestRequest(); long timeCounter = 100; enqueueOrReplaceBroadcast(queue, @@ -814,6 +836,28 @@ public final class BroadcastQueueModernImplTest { queue.makeActiveNextPending(); assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE, queue.getActive().intent.getAction()); + + + queue.removePrioritizeEarliestRequest(); + + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), + 0, timeCounter++); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++); + + // Once the request to prioritize earliest is removed, we should expect broadcasts + // to be dispatched in the order of foreground, normal and then offload. + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction()); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 7be1d7cde27f..ad5f0d7233ca 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -25,12 +25,15 @@ import static com.android.server.am.BroadcastProcessQueue.reasonToString; import static com.android.server.am.BroadcastRecord.deliveryStateToString; import static com.android.server.am.BroadcastRecord.isReceiverEquals; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -1890,6 +1893,36 @@ public class BroadcastQueueTest { assertTrue(mQueue.isBeyondBarrierLocked(afterSecond)); } + @Test + public void testWaitForBroadcastDispatch() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertTrue(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + + assertFalse(mQueue.isDispatchedLocked(timeTick)); + assertFalse(mQueue.isDispatchedLocked(timezone)); + + mLooper.release(); + + mQueue.waitForDispatched(timeTick, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timeTick)); + + mQueue.waitForDispatched(timezone, LOG_WRITER_INFO); + assertTrue(mQueue.isDispatchedLocked(timezone)); + } + /** * Verify that we OOM adjust for manifest receivers. */ @@ -2102,4 +2135,75 @@ public class BroadcastQueueTest { waitForIdle(); verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane); } + + @Test + public void testBroadcastDelivery_uidForeground() throws Exception { + // Legacy stack doesn't support prioritization to foreground app. + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + + mUidObserver.onUidStateChanged(receiverGreenApp.info.uid, + ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + + final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp); + final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp); + final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, callerApp, + List.of(receiverBlue)); + final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, callerApp, + List.of(receiverBlue, receiverGreen)); + + enqueueBroadcast(airplaneRecord); + enqueueBroadcast(timeTickRecord); + + waitForIdle(); + // Verify that broadcasts to receiverGreenApp gets scheduled first. + assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen)) + .isLessThan(getReceiverScheduledTime(airplaneRecord, receiverBlue)); + assertThat(getReceiverScheduledTime(timeTickRecord, receiverGreen)) + .isLessThan(getReceiverScheduledTime(timeTickRecord, receiverBlue)); + } + + @Test + public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception { + // Legacy stack doesn't support prioritization to foreground app. + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + + mUidObserver.onUidStateChanged(receiverGreenApp.info.uid, + ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + + final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 10); + final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 5); + final BroadcastRecord prioritizedRecord = makeBroadcastRecord(timeTick, callerApp, + List.of(receiverBlue, receiverGreen)); + + enqueueBroadcast(prioritizedRecord); + + waitForIdle(); + // Verify that uid foreground-ness does not impact that delivery of prioritized broadcast. + // That is, broadcast to receiverBlueApp gets scheduled before the one to receiverGreenApp. + assertThat(getReceiverScheduledTime(prioritizedRecord, receiverGreen)) + .isGreaterThan(getReceiverScheduledTime(prioritizedRecord, receiverBlue)); + } + + private long getReceiverScheduledTime(@NonNull BroadcastRecord r, @NonNull Object receiver) { + for (int i = 0; i < r.receivers.size(); ++i) { + if (isReceiverEquals(receiver, r.receivers.get(i))) { + return r.scheduledTime[i]; + } + } + fail(receiver + "not found in " + r); + return -1; + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java index b203cf640097..f99e156ed139 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -63,6 +63,7 @@ import com.android.server.SystemService; import com.android.server.backup.utils.RandomAccessFileUtils; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -615,6 +616,53 @@ public class BackupManagerServiceTest { verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); } + @Test + public void testDumpForOneUser_callerDoesNotHaveInteractAcrossUsersFullPermission_ignored() { + createBackupManagerServiceAndUnlockSystemUser(); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); + + String[] args = new String[]{"--user", Integer.toString(NON_SYSTEM_USER)}; + Assert.assertThrows(SecurityException.class, + () -> mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, + args)); + + verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); + } + + @Test + public void + testDumpForOneUser_callerHasInteractAcrossUsersFullPermission_dumpsOnlySpecifiedUser() { + createBackupManagerServiceAndUnlockSystemUser(); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + + String[] args = new String[]{"--user", Integer.toString(UserHandle.USER_SYSTEM)}; + mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args); + + verify(mSystemUserBackupManagerService).dump(any(), any(), any()); + } + + @Test + public void testDumpForAllUsers_callerHasInteractAcrossUsersFullPermission_dumpsAllUsers() { + createBackupManagerServiceAndUnlockSystemUser(); + mService.setBackupServiceActive(NON_SYSTEM_USER, true); + simulateUserUnlocked(NON_SYSTEM_USER); + + String[] args = new String[]{"users"}; + mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args); + + // Check that dump() invocations are not called on user's Backup service, + // as 'dumpsys backup users' only list users for whom Backup service is running. + verify(mSystemUserBackupManagerService, never()).dump(any(), any(), any()); + verify(mNonSystemUserBackupManagerService, never()).dump(any(), any(), any()); + } + /** * Test that {@link BackupManagerService#dump(FileDescriptor, PrintWriter, String[])} dumps * system user information before non-system user information. 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 f8955edab1d7..3d0163d84929 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -172,12 +172,12 @@ public class WallpaperManagerServiceTests { sImageWallpaperComponentName = ComponentName.unflattenFromString( sContext.getResources().getString(R.string.image_wallpaper_component)); // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper. - sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext); + sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext); if (sDefaultWallpaperComponent == null) { sDefaultWallpaperComponent = sImageWallpaperComponentName; doReturn(sImageWallpaperComponentName).when(() -> - WallpaperManager.getDefaultWallpaperComponent(any())); + WallpaperManager.getCmfDefaultWallpaperComponent(any())); } else { sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService); } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java index 11e4120e77e6..37ebdda9efc8 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java @@ -208,8 +208,8 @@ public class MagnificationControllerTest { mMockConnection = new MockWindowMagnificationConnection(true); mWindowMagnificationManager.setConnection(mMockConnection.getConnection()); - mMagnificationController = new MagnificationController(mService, globalLock, mContext, - mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider); + mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext, + mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider)); mMagnificationController.setMagnificationCapabilities( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL); @@ -593,6 +593,10 @@ public class MagnificationControllerTest { // The second time is triggered when magnification spec is changed. verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -758,6 +762,10 @@ public class MagnificationControllerTest { // The second time is triggered when accessibility action performed. verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -772,17 +780,27 @@ public class MagnificationControllerTest { // The first time is triggered when window mode is activated. // The second time is triggered when accessibility action performed. verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } + @Test public void activateWindowMagnification_triggerCallback() throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + verify(mMagnificationController).onWindowMagnificationActivationState( + eq(TEST_DISPLAY), eq(true)); + } @Test - public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, false); + public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage() + throws RemoteException { + setMagnificationEnabled(MODE_WINDOW); + mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true); - verify(spyController).logMagnificationUsageState( - eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong()); + verify(mMagnificationController).onWindowMagnificationActivationState( + eq(TEST_DISPLAY), eq(false)); + verify(mMagnificationController).logMagnificationUsageState( + eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong(), eq(DEFAULT_SCALE)); } @Test @@ -906,15 +924,22 @@ public class MagnificationControllerTest { assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode); } - @Test - public void onFullScreenMagnificationActivationState_fullScreenEnabled_logFullScreenDuration() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + verify(mMagnificationController).onFullScreenMagnificationActivationState( + eq(TEST_DISPLAY), eq(true)); + } - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false); + @Test + public void deactivateFullScreenMagnification_fullScreenEnabled_triggerCallbackAndLogUsage() + throws RemoteException { + setMagnificationEnabled(MODE_FULLSCREEN); + mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ false); - verify(spyController).logMagnificationUsageState( - eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong()); + verify(mMagnificationController).onFullScreenMagnificationActivationState( + eq(TEST_DISPLAY), eq(false)); + verify(mMagnificationController).logMagnificationUsageState( + eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong(), eq(DEFAULT_SCALE)); } @Test @@ -939,6 +964,10 @@ public class MagnificationControllerTest { // The third time is triggered when user interaction changed. verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -953,6 +982,10 @@ public class MagnificationControllerTest { // The third time is triggered when user interaction changed. verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -966,6 +999,10 @@ public class MagnificationControllerTest { // The second time is triggered when user interaction changed. verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -979,6 +1016,10 @@ public class MagnificationControllerTest { // The second time is triggered when user interaction changed. verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -993,11 +1034,16 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // The first time is triggered when fullscreen mode is activated. + // The second time is triggered when magnification spec is changed. + verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test - public void onTouchInteractionChanged_fullscreenNotActivated_notShowMagnificationButton() + public void + onTouchInteractionChanged_fullscreenNotActivated_notShowMagnificationButton() throws RemoteException { setMagnificationModeSettings(MODE_FULLSCREEN); @@ -1006,6 +1052,8 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager, never()).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + verify(mWindowMagnificationManager, times(2)).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -1015,6 +1063,10 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test @@ -1029,25 +1081,32 @@ public class MagnificationControllerTest { // The third time is triggered when fullscreen mode activation state is updated. verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // Never call removeMagnificationSettingsPanel if it is allowed to show the settings panel + // in current capability and mode, and the magnification is activated. + verify(mWindowMagnificationManager, never()).removeMagnificationSettingsPanel( + eq(TEST_DISPLAY)); } @Test - public void disableWindowMode_windowEnabled_removeMagnificationButton() + public void disableWindowMode_windowEnabled_removeMagnificationButtonAndSettingsPanel() throws RemoteException { setMagnificationEnabled(MODE_WINDOW); mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, false); verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); + verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test - public void onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButton() + public void + onFullScreenDeactivated_fullScreenEnabled_removeMagnificationButtonAneSettingsPanel() throws RemoteException { setMagnificationEnabled(MODE_FULLSCREEN); mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ true); verify(mWindowMagnificationManager).removeMagnificationButton(eq(TEST_DISPLAY)); + verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1064,6 +1123,8 @@ public class MagnificationControllerTest { // The third time is triggered when the disable-magnification callback is triggered. verify(mWindowMagnificationManager, times(3)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_FULLSCREEN)); + // It is triggered when the disable-magnification callback is triggered. + verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1083,6 +1144,8 @@ public class MagnificationControllerTest { // The second time is triggered when the disable-magnification callback is triggered. verify(mWindowMagnificationManager, times(2)).showMagnificationButton(eq(TEST_DISPLAY), eq(MODE_WINDOW)); + // It is triggered when the disable-magnification callback is triggered. + verify(mWindowMagnificationManager).removeMagnificationSettingsPanel(eq(TEST_DISPLAY)); } @Test @@ -1106,48 +1169,44 @@ public class MagnificationControllerTest { @Test public void imeWindowStateShown_windowMagnifying_logWindowMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController).logMagnificationModeWithIme( + verify(mMagnificationController).logMagnificationModeWithIme( eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW)); } @Test public void imeWindowStateShown_fullScreenMagnifying_logFullScreenMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController).logMagnificationModeWithIme( + verify(mMagnificationController).logMagnificationModeWithIme( eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)); } @Test public void imeWindowStateShown_noMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); + mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test public void imeWindowStateHidden_windowMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onFullScreenMagnificationActivationState( + TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test public void imeWindowStateHidden_fullScreenMagnifying_noLogAnyMode() { - MagnificationController spyController = spy(mMagnificationController); - spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true); + mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true); - verify(spyController, never()).logMagnificationModeWithIme(anyInt()); + verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 2ea56f659297..39de2cf86a6c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -8600,6 +8600,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { private void setUserUnlocked(int userHandle, boolean unlocked) { when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked); + when(getServices().userManagerInternal.isUserUnlockingOrUnlocked(eq(userHandle))) + .thenReturn(unlocked); } private void prepareMocksForSetMaximumProfileTimeOff() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index 8b178dd9b3e1..7641fb957cc8 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -373,6 +373,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_succeedsWithCertFile() throws Exception { int uid = Binder.getCallingUid(); @@ -394,6 +395,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_updatesShouldCreatesnapshotOnCertUpdate() throws Exception { int uid = Binder.getCallingUid(); @@ -421,6 +423,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_triesToFilterRootAlias() throws Exception { int uid = Binder.getCallingUid(); @@ -442,6 +445,7 @@ public class RecoverableKeyStoreManagerTest { } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_usesProdCertificateForEmptyRootAlias() throws Exception { int uid = Binder.getCallingUid(); @@ -462,6 +466,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_usesProdCertificateForNullRootAlias() throws Exception { int uid = Binder.getCallingUid(); @@ -482,6 +487,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(activeRootAlias).isEqualTo(DEFAULT_ROOT_CERT_ALIAS); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_regeneratesCounterId() throws Exception { int uid = Binder.getCallingUid(); @@ -512,6 +518,7 @@ public class RecoverableKeyStoreManagerTest { } } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_updatesWithLargerSerial() throws Exception { int uid = Binder.getCallingUid(); @@ -529,6 +536,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_throwsExceptionOnSmallerSerial() throws Exception { long certSerial = 1000L; @@ -592,6 +600,7 @@ public class RecoverableKeyStoreManagerTest { TestData.getInsecureCertPathForEndpoint1()); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryService_ignoresTheSameSerial() throws Exception { int uid = Binder.getCallingUid(); @@ -642,6 +651,7 @@ public class RecoverableKeyStoreManagerTest { } } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryServiceWithSigFile_succeeds() throws Exception { int uid = Binder.getCallingUid(); @@ -657,6 +667,7 @@ public class RecoverableKeyStoreManagerTest { assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void initRecoveryServiceWithSigFile_usesProdCertificateForNullRootAlias() throws Exception { @@ -749,6 +760,7 @@ public class RecoverableKeyStoreManagerTest { eq(Manifest.permission.RECOVER_KEYSTORE), any()); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception { mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( @@ -766,6 +778,7 @@ public class RecoverableKeyStoreManagerTest { assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception { mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( @@ -869,6 +882,7 @@ public class RecoverableKeyStoreManagerTest { } } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets() throws Exception { try { @@ -886,6 +900,7 @@ public class RecoverableKeyStoreManagerTest { } } + @Ignore("Causing breakages so ignoring to resolve, b/281583079") @Test public void startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch() throws Exception { byte[] vaultParams = TEST_VAULT_PARAMS.clone(); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java index 2a9c18c55035..bbd9223718ae 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java @@ -104,7 +104,7 @@ public class RecoverableKeyStoreDbHelperTest { @Before public void setUp() throws Exception { Context context = InstrumentationRegistry.getTargetContext(); - mDatabaseHelper = new RecoverableKeyStoreDbHelper(context, 7); + mDatabaseHelper = new RecoverableKeyStoreDbHelper(context); mDatabase = SQLiteDatabase.create(null); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java index e223a97d53f7..8bc14fc54ae1 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -67,7 +67,7 @@ public class RecoverableKeyStoreDbTest { public void setUp() { Context context = InstrumentationRegistry.getTargetContext(); mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); - mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context, 7); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); } @After diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java new file mode 100644 index 000000000000..cba7dbe2d02b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java @@ -0,0 +1,154 @@ +/* + * 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.server.power.stats.wakeups; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.SparseIntArray; +import android.util.TimeSparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.server.power.stats.wakeups.CpuWakeupStats.WakingActivityHistory; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class WakingActivityHistoryTest { + + private static boolean areSame(SparseIntArray a, SparseIntArray b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + final int lenA = a.size(); + if (b.size() != lenA) { + return false; + } + for (int i = 0; i < lenA; i++) { + if (a.keyAt(i) != b.keyAt(i)) { + return false; + } + if (a.valueAt(i) != b.valueAt(i)) { + return false; + } + } + return true; + } + + @Test + public void recordActivityAppendsUids() { + final WakingActivityHistory history = new WakingActivityHistory(); + final int subsystem = 42; + final long timestamp = 54; + + final SparseIntArray uids = new SparseIntArray(); + uids.put(1, 3); + uids.put(5, 2); + + history.recordActivity(subsystem, timestamp, uids); + + assertThat(history.mWakingActivity.size()).isEqualTo(1); + assertThat(history.mWakingActivity.contains(subsystem)).isTrue(); + assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse(); + assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse(); + + final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( + subsystem); + + assertThat(recordedHistory.size()).isEqualTo(1); + assertThat(recordedHistory.indexOfKey(timestamp - 1)).isLessThan(0); + assertThat(recordedHistory.indexOfKey(timestamp)).isAtLeast(0); + assertThat(recordedHistory.indexOfKey(timestamp + 1)).isLessThan(0); + + SparseIntArray recordedUids = recordedHistory.get(timestamp); + assertThat(recordedUids).isNotSameInstanceAs(uids); + assertThat(areSame(recordedUids, uids)).isTrue(); + + uids.put(1, 7); + uids.clear(); + uids.put(10, 12); + uids.put(17, 53); + + history.recordActivity(subsystem, timestamp, uids); + + recordedUids = recordedHistory.get(timestamp); + + assertThat(recordedUids.size()).isEqualTo(4); + assertThat(recordedUids.indexOfKey(1)).isAtLeast(0); + assertThat(recordedUids.get(5, -1)).isEqualTo(2); + assertThat(recordedUids.get(10, -1)).isEqualTo(12); + assertThat(recordedUids.get(17, -1)).isEqualTo(53); + } + + @Test + public void recordActivityDoesNotDeleteExistingUids() { + final WakingActivityHistory history = new WakingActivityHistory(); + final int subsystem = 42; + long timestamp = 101; + + final SparseIntArray uids = new SparseIntArray(); + uids.put(1, 17); + uids.put(15, 2); + uids.put(62, 31); + + history.recordActivity(subsystem, timestamp, uids); + + assertThat(history.mWakingActivity.size()).isEqualTo(1); + assertThat(history.mWakingActivity.contains(subsystem)).isTrue(); + assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse(); + assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse(); + + final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( + subsystem); + + assertThat(recordedHistory.size()).isEqualTo(1); + assertThat(recordedHistory.indexOfKey(timestamp - 1)).isLessThan(0); + assertThat(recordedHistory.indexOfKey(timestamp)).isAtLeast(0); + assertThat(recordedHistory.indexOfKey(timestamp + 1)).isLessThan(0); + + SparseIntArray recordedUids = recordedHistory.get(timestamp); + assertThat(recordedUids).isNotSameInstanceAs(uids); + assertThat(areSame(recordedUids, uids)).isTrue(); + + uids.delete(1); + uids.delete(15); + uids.put(85, 39); + + history.recordActivity(subsystem, timestamp, uids); + recordedUids = recordedHistory.get(timestamp); + + assertThat(recordedUids.size()).isEqualTo(4); + assertThat(recordedUids.get(1, -1)).isEqualTo(17); + assertThat(recordedUids.get(15, -1)).isEqualTo(2); + assertThat(recordedUids.get(62, -1)).isEqualTo(31); + assertThat(recordedUids.get(85, -1)).isEqualTo(39); + + uids.clear(); + history.recordActivity(subsystem, timestamp, uids); + recordedUids = recordedHistory.get(timestamp); + + assertThat(recordedUids.size()).isEqualTo(4); + assertThat(recordedUids.get(1, -1)).isEqualTo(17); + assertThat(recordedUids.get(15, -1)).isEqualTo(2); + assertThat(recordedUids.get(62, -1)).isEqualTo(31); + assertThat(recordedUids.get(85, -1)).isEqualTo(39); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ff6c534b81ed..7d028d21ce0a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5555,6 +5555,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testVisitUris() throws Exception { final Uri audioContents = Uri.parse("content://com.example/audio"); final Uri backgroundImage = Uri.parse("content://com.example/background"); + final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon"); + final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon"); final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); @@ -5588,7 +5590,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Notification n = new Notification.Builder(mContext, "a") .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setSmallIcon(smallIcon) + .setLargeIcon(largeIcon) .addExtras(extras) .build(); @@ -5596,6 +5599,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.visitUris(visitor); verify(visitor, times(1)).accept(eq(audioContents)); verify(visitor, times(1)).accept(eq(backgroundImage)); + verify(visitor, times(1)).accept(eq(smallIcon.getUri())); + verify(visitor, times(1)).accept(eq(largeIcon.getUri())); verify(visitor, times(1)).accept(eq(personIcon1.getUri())); verify(visitor, times(1)).accept(eq(personIcon2.getUri())); verify(visitor, times(1)).accept(eq(personIcon3.getUri())); @@ -5604,6 +5609,68 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisitUris_audioContentsString() throws Exception { + final Uri audioContents = Uri.parse("content://com.example/audio"); + + Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); + + Notification n = new Notification.Builder(mContext, "a") + .setContentTitle("notification with uris") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .addExtras(extras) + .build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + verify(visitor, times(1)).accept(eq(audioContents)); + } + + @Test + public void testVisitUris_messagingStyle() { + final Icon personIcon1 = Icon.createWithContentUri("content://media/person1"); + final Icon personIcon2 = Icon.createWithContentUri("content://media/person2"); + final Icon personIcon3 = Icon.createWithContentUri("content://media/person3"); + final Person person1 = new Person.Builder() + .setName("Messaging Person 1") + .setIcon(personIcon1) + .build(); + final Person person2 = new Person.Builder() + .setName("Messaging Person 2") + .setIcon(personIcon2) + .build(); + final Person person3 = new Person.Builder() + .setName("Messaging Person 3") + .setIcon(personIcon3) + .build(); + Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut"); + + Notification.Builder builder = new Notification.Builder(mContext, "a") + .setCategory(Notification.CATEGORY_MESSAGE) + .setContentTitle("new message!") + .setContentText("Conversation Notification") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message( + "Marco?", System.currentTimeMillis(), person2); + Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message( + "Polo!", System.currentTimeMillis(), person3); + Notification.MessagingStyle style = new Notification.MessagingStyle(person1) + .addMessage(message1) + .addMessage(message2) + .setShortcutIcon(shortcutIcon); + builder.setStyle(style); + Notification n = builder.build(); + + Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); + n.visitUris(visitor); + + verify(visitor, times(1)).accept(eq(shortcutIcon.getUri())); + verify(visitor, times(1)).accept(eq(personIcon1.getUri())); + verify(visitor, times(1)).accept(eq(personIcon2.getUri())); + verify(visitor, times(1)).accept(eq(personIcon3.getUri())); + } + + @Test public void testVisitUris_callStyle() { Icon personIcon = Icon.createWithContentUri("content://media/person"); Icon verificationIcon = Icon.createWithContentUri("content://media/verification"); @@ -5627,24 +5694,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testVisitUris_audioContentsString() throws Exception { - final Uri audioContents = Uri.parse("content://com.example/audio"); - - Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString()); - - Notification n = new Notification.Builder(mContext, "a") - .setContentTitle("notification with uris") - .setSmallIcon(android.R.drawable.sym_def_app_icon) - .addExtras(extras) - .build(); - - Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class); - n.visitUris(visitor); - verify(visitor, times(1)).accept(eq(audioContents)); - } - - @Test public void testSetNotificationPolicy_preP_setOldFields() { ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = mZenModeHelper; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java new file mode 100644 index 000000000000..27677e153d83 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java @@ -0,0 +1,778 @@ +/* + * 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.server.notification; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.truth.Truth.assertThat; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Person; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.Icon; +import android.media.session.MediaSession; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import androidx.annotation.NonNull; +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.UiServiceTestCase; + +import com.google.common.base.Strings; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.truth.Expect; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +@RunWith(AndroidJUnit4.class) +public class NotificationVisitUrisTest extends UiServiceTestCase { + + private static final String TAG = "VisitUrisTest"; + + // Methods that are known to add Uris that are *NOT* verified. + // This list should be emptied! Items can be removed as bugs are fixed. + private static final Multimap<Class<?>, String> KNOWN_BAD = + ImmutableMultimap.<Class<?>, String>builder() + .put(Notification.Builder.class, "setPublicVersion") // b/276294099 + .putAll(RemoteViews.class, "addView", "addStableView") // b/277740082 + .put(RemoteViews.class, "setIcon") // b/281018094 + .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385 + .put(Person.Builder.class, "setUri") // TODO: b/281044385 + .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385 + .build(); + + // Types that we can't really produce. No methods receiving these parameters will be invoked. + private static final ImmutableSet<Class<?>> UNUSABLE_TYPES = + ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class, + PrintWriter.class, Resources.Theme.class, View.class); + + // Maximum number of times we allow generating the same class recursively. + // E.g. new RemoteViews.addView(new RemoteViews()) but stop there. + private static final int MAX_RECURSION = 2; + + // Number of times a method called addX(X) will be called. + private static final int NUM_ADD_CALLS = 2; + + // Number of elements to put in a generated array, e.g. for calling setGloops(Gloop[] gloops). + private static final int NUM_ELEMENTS_IN_ARRAY = 3; + + // Constructors that should be used to create instances of specific classes. Overrides scoring. + private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS; + + static { + try { + PREFERRED_CONSTRUCTORS = ImmutableMap.of( + Notification.Builder.class, + Notification.Builder.class.getConstructor(Context.class, String.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private static final Multimap<Class<?>, String> EXCLUDED_SETTERS = + ImmutableMultimap.<Class<?>, String>builder() + // Handled by testAllStyles(). + .put(Notification.Builder.class, "setStyle") + // Handled by testAllExtenders(). + .put(Notification.Builder.class, "extend") + // Handled by testAllActionExtenders(). + .put(Notification.Action.Builder.class, "extend") + // Overwrites icon supplied to constructor. + .put(Notification.BubbleMetadata.Builder.class, "setIcon") + // Discards previously-added actions. + .put(RemoteViews.class, "mergeRemoteViews") + .build(); + + private Context mContext; + + @Rule + public final Expect expect = Expect.create(); + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test // This is a meta-test, checks that the generators are not broken. + public void verifyTest() { + Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null, + /* extenderClass= */ null, /* actionExtenderClass= */ null, + /* includeRemoteViews= */ true); + assertThat(notification.includedUris.size()).isAtLeast(20); + } + + @Test + public void testPlainNotification() throws Exception { + Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null, + /* extenderClass= */ null, /* actionExtenderClass= */ null, + /* includeRemoteViews= */ false); + verifyAllUrisAreVisited(notification.value, notification.includedUris, + "Plain Notification"); + } + + @Test + public void testRemoteViews() throws Exception { + Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null, + /* extenderClass= */ null, /* actionExtenderClass= */ null, + /* includeRemoteViews= */ true); + verifyAllUrisAreVisited(notification.value, notification.includedUris, + "Notification with Remote Views"); + } + + @Test + public void testAllStyles() throws Exception { + for (Class<?> styleClass : ReflectionUtils.getConcreteSubclasses(Notification.Style.class, + Notification.class)) { + Generated<Notification> notification = buildNotification(mContext, styleClass, + /* extenderClass= */ null, /* actionExtenderClass= */ null, + /* includeRemoteViews= */ false); + verifyAllUrisAreVisited(notification.value, notification.includedUris, + String.format("Style (%s)", styleClass.getSimpleName())); + } + } + + @Test + public void testAllExtenders() throws Exception { + for (Class<?> extenderClass : ReflectionUtils.getConcreteSubclasses( + Notification.Extender.class, Notification.class)) { + Generated<Notification> notification = buildNotification(mContext, + /* styleClass= */ null, extenderClass, /* actionExtenderClass= */ null, + /* includeRemoteViews= */ false); + verifyAllUrisAreVisited(notification.value, notification.includedUris, + String.format("Extender (%s)", extenderClass.getSimpleName())); + } + } + + @Test + public void testAllActionExtenders() throws Exception { + for (Class<?> actionExtenderClass : ReflectionUtils.getConcreteSubclasses( + Notification.Action.Extender.class, Notification.Action.class)) { + Generated<Notification> notification = buildNotification(mContext, + /* styleClass= */ null, /* extenderClass= */ null, actionExtenderClass, + /* includeRemoteViews= */ false); + verifyAllUrisAreVisited(notification.value, notification.includedUris, + String.format("Action.Extender (%s)", actionExtenderClass.getSimpleName())); + } + } + + private void verifyAllUrisAreVisited(Notification notification, List<Uri> includedUris, + String notificationTypeMessage) throws Exception { + Consumer<Uri> visitor = (Consumer<Uri>) Mockito.mock(Consumer.class); + ArgumentCaptor<Uri> visitedUriCaptor = ArgumentCaptor.forClass(Uri.class); + + notification.visitUris(visitor); + + Mockito.verify(visitor, Mockito.atLeastOnce()).accept(visitedUriCaptor.capture()); + List<Uri> visitedUris = new ArrayList<>(visitedUriCaptor.getAllValues()); + visitedUris.remove(null); + + expect.withMessage(notificationTypeMessage) + .that(visitedUris) + .containsAtLeastElementsIn(includedUris); + expect.that(KNOWN_BAD).isNotEmpty(); // Once empty, switch to containsExactlyElementsIn() + } + + private static Generated<Notification> buildNotification(Context context, + @Nullable Class<?> styleClass, @Nullable Class<?> extenderClass, + @Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) { + SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context); + Set<Class<?>> excludedClasses = includeRemoteViews + ? ImmutableSet.of() + : ImmutableSet.of(RemoteViews.class); + Location location = Location.root(Notification.Builder.class); + + Notification.Builder builder = new Notification.Builder(context, "channelId"); + invokeAllSetters(builder, location, /* allOverloads= */ false, + /* includingVoidMethods= */ false, excludedClasses, specialGenerator); + + if (styleClass != null) { + builder.setStyle((Notification.Style) generateObject(styleClass, + location.plus("setStyle", Notification.Style.class), + excludedClasses, specialGenerator)); + } + if (extenderClass != null) { + builder.extend((Notification.Extender) generateObject(extenderClass, + location.plus("extend", Notification.Extender.class), + excludedClasses, specialGenerator)); + } + if (actionExtenderClass != null) { + Location actionLocation = location.plus("addAction", Notification.Action.class); + Notification.Action.Builder actionBuilder = + (Notification.Action.Builder) generateObject( + Notification.Action.Builder.class, actionLocation, excludedClasses, + specialGenerator); + actionBuilder.extend((Notification.Action.Extender) generateObject(actionExtenderClass, + actionLocation.plus( + Notification.Action.Builder.class).plus("extend", + Notification.Action.Extender.class), + excludedClasses, specialGenerator)); + builder.addAction(actionBuilder.build()); + } + + return new Generated<>(builder.build(), specialGenerator.getGeneratedUris()); + } + + private static Object generateObject(Class<?> clazz, Location where, + Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + if (excludingClasses.contains(clazz)) { + throw new IllegalArgumentException( + String.format("Asked to generate a %s but it's part of the excluded set (%s)", + clazz, excludingClasses)); + } + + if (SpecialParameterGenerator.canGenerate(clazz)) { + return specialGenerator.generate(clazz, where); + } + if (clazz.isEnum()) { + return clazz.getEnumConstants()[0]; + } + if (clazz.isArray()) { + Object arrayValue = Array.newInstance(clazz.getComponentType(), NUM_ELEMENTS_IN_ARRAY); + for (int i = 0; i < Array.getLength(arrayValue); i++) { + Array.set(arrayValue, i, + generateObject(clazz.getComponentType(), where, excludingClasses, + specialGenerator)); + } + return arrayValue; + } + + Log.i(TAG, "About to generate a(n)" + clazz.getName()); + + // Need to construct one of these. Look for a Builder inner class... and also look for a + // Builder as a "sibling" class; CarExtender.UnreadConversation does this :( + Stream<Class<?>> maybeBuilders = + Stream.concat(Arrays.stream(clazz.getDeclaredClasses()), + clazz.getDeclaringClass() != null + ? Arrays.stream(clazz.getDeclaringClass().getDeclaredClasses()) + : Stream.empty()); + Optional<Class<?>> clazzBuilder = + maybeBuilders + .filter(maybeBuilder -> maybeBuilder.getSimpleName().equals("Builder")) + .filter(maybeBuilder -> + Arrays.stream(maybeBuilder.getMethods()).anyMatch( + m -> m.getName().equals("build") + && m.getParameterCount() == 0 + && m.getReturnType().equals(clazz))) + .findFirst(); + + + if (clazzBuilder.isPresent()) { + try { + // Found a Builder! Create an instance of it, call its setters, and call build() + // on it. + Object builder = constructEmpty(clazzBuilder.get(), where.plus(clazz), + excludingClasses, specialGenerator); + invokeAllSetters(builder, where.plus(clazz).plus(clazzBuilder.get()), + /* allOverloads= */ false, /* includingVoidMethods= */ false, + excludingClasses, specialGenerator); + + Method buildMethod = builder.getClass().getMethod("build"); + Object built = buildMethod.invoke(builder); + assertThat(built).isInstanceOf(clazz); + return built; + } catch (Exception e) { + throw new UnsupportedOperationException( + "Error using Builder " + clazzBuilder.get().getName(), e); + } + } + + // If no X.Builder, look for X() constructor. + try { + Object instance = constructEmpty(clazz, where, excludingClasses, specialGenerator); + invokeAllSetters(instance, where.plus(clazz), /* allOverloads= */ false, + /* includingVoidMethods= */ false, excludingClasses, specialGenerator); + return instance; + } catch (Exception e) { + throw new UnsupportedOperationException("Error generating a(n) " + clazz.getName(), e); + } + } + + private static Object constructEmpty(Class<?> clazz, Location where, + Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + Constructor<?> bestConstructor; + if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) { + // Use the preferred constructor. + bestConstructor = PREFERRED_CONSTRUCTORS.get(clazz); + } else if (Notification.Extender.class.isAssignableFrom(clazz) + || Notification.Action.Extender.class.isAssignableFrom(clazz)) { + // For extenders, prefer the empty constructors. The others are "partial-copy" + // constructors and do not read all fields from the supplied Notification/Action. + try { + bestConstructor = clazz.getConstructor(); + } catch (Exception e) { + throw new UnsupportedOperationException( + String.format("Extender class %s doesn't have a zero-parameter constructor", + clazz.getName())); + } + } else { + // Look for a non-deprecated constructor using any of the "interesting" parameters. + List<Constructor<?>> allConstructors = Arrays.stream(clazz.getConstructors()) + .filter(c -> c.getAnnotation(Deprecated.class) == null) + .collect(Collectors.toList()); + bestConstructor = ReflectionUtils.chooseBestOverload(allConstructors, where); + } + if (bestConstructor != null) { + try { + Object[] constructorParameters = generateParameters(bestConstructor, + where.plus(clazz), excludingClasses, specialGenerator); + Log.i(TAG, "Invoking " + ReflectionUtils.methodToString(bestConstructor) + " with " + + Arrays.toString(constructorParameters)); + return bestConstructor.newInstance(constructorParameters); + } catch (Exception e) { + throw new UnsupportedOperationException( + String.format("Error invoking constructor %s", + ReflectionUtils.methodToString(bestConstructor)), e); + } + } + + // Look for a "static constructor", i.e. some factory method on the same class. + List<Method> factoryMethods = Arrays.stream(clazz.getMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers()) && clazz.equals(m.getReturnType())) + .collect(Collectors.toList()); + Method bestFactoryMethod = ReflectionUtils.chooseBestOverload(factoryMethods, where); + if (bestFactoryMethod != null) { + try { + Object[] methodParameters = generateParameters(bestFactoryMethod, where.plus(clazz), + excludingClasses, specialGenerator); + Log.i(TAG, + "Invoking " + ReflectionUtils.methodToString(bestFactoryMethod) + " with " + + Arrays.toString(methodParameters)); + return bestFactoryMethod.invoke(null, methodParameters); + } catch (Exception e) { + throw new UnsupportedOperationException( + "Error invoking constructor-like static method " + + bestFactoryMethod.getName() + " for " + clazz.getName(), e); + } + } + + throw new UnsupportedOperationException( + "Couldn't find a way to construct a(n) " + clazz.getName()); + } + + private static void invokeAllSetters(Object instance, Location where, boolean allOverloads, + boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes, + SpecialParameterGenerator specialGenerator) { + for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where, + allOverloads, includingVoidMethods, excludingParameterTypes)) { + try { + int numInvocations = setter.getName().startsWith("add") ? NUM_ADD_CALLS : 1; + for (int i = 0; i < numInvocations; i++) { + + // If the method is a "known bad" (i.e. adds Uris that aren't visited later) + // then still call it, but don't add to list of generated Uris. Easiest way is + // to use a throw-away SpecialParameterGenerator instead of the accumulating + // one. + SpecialParameterGenerator specialGeneratorForThisSetter = + KNOWN_BAD.containsEntry(instance.getClass(), setter.getName()) + ? new SpecialParameterGenerator(specialGenerator.mContext) + : specialGenerator; + + Object[] setterParam = generateParameters(setter, where, + excludingParameterTypes, specialGeneratorForThisSetter); + Log.i(TAG, "Invoking " + ReflectionUtils.methodToString(setter) + " with " + + setterParam[0]); + setter.invoke(instance, setterParam); + } + } catch (Exception e) { + throw new UnsupportedOperationException( + "Error invoking setter " + ReflectionUtils.methodToString(setter), e); + } + } + } + + private static Object[] generateParameters(Executable executable, Location where, + Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) { + Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable) + + " in " + where); + Class<?>[] parameterTypes = executable.getParameterTypes(); + Object[] parameterValues = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + parameterValues[i] = generateObject( + parameterTypes[i], + where.plus(executable, + String.format("[%d,%s]", i, parameterTypes[i].getName())), + excludingClasses, + specialGenerator); + } + return parameterValues; + } + + private static class ReflectionUtils { + static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) { + return Arrays.stream(containerClass.getDeclaredClasses()) + .filter( + innerClass -> clazz.isAssignableFrom(innerClass) + && !Modifier.isAbstract(innerClass.getModifiers())) + .collect(Collectors.toSet()); + } + + static String methodToString(Executable executable) { + return String.format("%s::%s(%s)", + executable.getDeclaringClass().getName(), + executable.getName(), + Arrays.stream(executable.getParameterTypes()).map(Class::getSimpleName) + .collect(Collectors.joining(", ")) + ); + } + + static List<Method> getAllSetters(Class<?> clazz, Location where, boolean allOverloads, + boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes) { + ListMultimap<String, Method> methods = ArrayListMultimap.create(); + // Candidate "setters" are any methods that receive one at least parameter and are + // either void (if acceptable) or return the same type being built. + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getAnnotation(Deprecated.class) == null + && ((includingVoidMethods && method.getReturnType().equals(Void.TYPE)) + || method.getReturnType().equals(clazz)) + && method.getParameterCount() >= 1 + && !EXCLUDED_SETTERS.containsEntry(clazz, method.getName()) + && Arrays.stream(method.getParameterTypes()) + .noneMatch(excludingParameterTypes::contains)) { + methods.put(method.getName(), method); + } + } + + // In case of overloads, prefer those with the most interesting parameters. + List<Method> setters = new ArrayList<>(); + for (String methodName : methods.keySet()) { + setters.addAll(chooseOverloads(methods.get(methodName), where, allOverloads)); + } + + // Exclude set(x[]) when there exists add(x). + List<Method> excludedSetters = setters.stream().filter( + m1 -> m1.getName().startsWith("set") + && setters.stream().anyMatch( + m2 -> { + Class<?> param1 = m1.getParameterTypes()[0]; + Class<?> param2 = m2.getParameterTypes()[0]; + return m2.getName().startsWith("add") + && param1.isArray() + && !param2.isArray() && !param2.isPrimitive() + && param1.getComponentType().equals(param2); + })).toList(); + + setters.removeAll(excludedSetters); + return setters; + } + + @Nullable + static <T extends Executable> T chooseBestOverload(List<T> executables, Location where) { + ImmutableList<T> chosen = chooseOverloads(executables, where, + /* chooseMultiple= */ false); + return (chosen.isEmpty() ? null : chosen.get(0)); + } + + static <T extends Executable> ImmutableList<T> chooseOverloads(List<T> executables, + Location where, boolean chooseMultiple) { + // Exclude variants with non-usable parameters and too-deep recursions. + executables = executables.stream() + .filter(e -> Arrays.stream(e.getParameterTypes()).noneMatch( + p -> UNUSABLE_TYPES.contains(p) + || where.getClassOccurrenceCount(p) >= MAX_RECURSION)) + .collect(Collectors.toList()); + + if (executables.size() <= 1) { + return ImmutableList.copyOf(executables); + } + + // Overloads in "builders" usually set the same thing in two different ways (e.g. + // x(Bitmap) and x(Icon)). We choose the one with the most "interesting" parameters + // (from the point of view of containing Uris). In case of ties, LEAST parameters win, + // to use the simplest. + ArrayList<T> sortedCopy = new ArrayList<>(executables); + sortedCopy.sort( + Comparator.comparingInt(ReflectionUtils::getMethodScore) + .thenComparing(Executable::getParameterCount) + .reversed()); + + return chooseMultiple + ? ImmutableList.copyOf(sortedCopy) + : ImmutableList.of(sortedCopy.get(0)); + } + + /** + * Counts the number of "interesting" parameters in a method. Used to choose the constructor + * or builder-setter overload most suited to this test (e.g. prefer + * {@link Notification.Builder#setLargeIcon(Icon)} to + * {@link Notification.Builder#setLargeIcon(Bitmap)}. + */ + static int getMethodScore(Executable executable) { + return Arrays.stream(executable.getParameterTypes()) + .mapToInt(SpecialParameterGenerator::getParameterScore).sum(); + } + } + + private static class SpecialParameterGenerator { + private static final ImmutableSet<Class<?>> INTERESTING_CLASSES = + ImmutableSet.of( + Person.class, Uri.class, Icon.class, Intent.class, PendingIntent.class, + RemoteViews.class); + private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of(); + + private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES = + ImmutableMap.<Class<?>, Object>builder() + .put(boolean.class, false) + .put(byte.class, (byte) 4) + .put(short.class, (short) 44) + .put(int.class, 1) + .put(long.class, 44444444L) + .put(float.class, 33.33f) + .put(double.class, 3333.3333d) + .put(char.class, 'N') + .build(); + + private final Context mContext; + private final List<Uri> mGeneratedUris = new ArrayList<>(); + private int mNextUriCounter = 1; + + SpecialParameterGenerator(Context context) { + mContext = context; + } + + static boolean canGenerate(Class<?> clazz) { + return (INTERESTING_CLASSES.contains(clazz) && !clazz.equals(Person.class)) + || MOCKED_CLASSES.contains(clazz) + || clazz.equals(Context.class) + || clazz.equals(Bundle.class) + || clazz.equals(Bitmap.class) + || clazz.isPrimitive() + || clazz.equals(CharSequence.class) || clazz.equals(String.class); + } + + static int getParameterScore(Class<?> parameterClazz) { + if (parameterClazz.isArray()) { + return getParameterScore(parameterClazz.getComponentType()); + } else if (INTERESTING_CLASSES.contains(parameterClazz)) { + return 10; + } else if (parameterClazz.isPrimitive() || parameterClazz.equals(CharSequence.class) + || parameterClazz.equals(String.class)) { + return 0; + } else { + // No idea. We don't deep inspect, but score them as better than known-useless. + return 1; + } + } + + Object generate(Class<?> clazz, Location where) { + if (clazz == Uri.class) { + return generateUri(where); + } + + // Interesting parameters + if (clazz == Icon.class) { + Uri iconUri = generateUri( + where.plus(Icon.class).plus("createWithContentUri", Uri.class)); + return Icon.createWithContentUri(iconUri); + } + + if (clazz == Intent.class) { + // TODO(b/281044385): Are Intent Uris (new Intent(String,Uri)) relevant? + return new Intent("action"); + } + + if (clazz == PendingIntent.class) { + // PendingIntent can have an Intent with a Uri but those are inaccessible and + // not inspected. + return PendingIntent.getActivity(mContext, 0, new Intent("action"), + PendingIntent.FLAG_IMMUTABLE); + } + + if (clazz == RemoteViews.class) { + RemoteViews rv = new RemoteViews(mContext.getPackageName(), /* layoutId= */ 10); + invokeAllSetters(rv, where.plus(RemoteViews.class), + /* allOverloads= */ true, /* includingVoidMethods= */ true, + /* excludingParameterTypes= */ ImmutableSet.of(), this); + return rv; + } + + if (MOCKED_CLASSES.contains(clazz)) { + return Mockito.mock(clazz); + } + if (clazz.equals(Context.class)) { + return mContext; + } + if (clazz.equals(Bundle.class)) { + return new Bundle(); + } + if (clazz.equals(Bitmap.class)) { + return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888); + } + + // ~Primitives + if (PRIMITIVE_VALUES.containsKey(clazz)) { + return PRIMITIVE_VALUES.get(clazz); + } + if (clazz.equals(CharSequence.class) || clazz.equals(String.class)) { + return where + "->string"; + } + + throw new IllegalArgumentException( + "I have no idea how to produce a(n) " + clazz + ", sorry"); + } + + private Uri generateUri(Location where) { + Uri uri = Uri.parse(String.format("%s - %s", mNextUriCounter++, where)); + mGeneratedUris.add(uri); + return uri; + } + + public List<Uri> getGeneratedUris() { + return mGeneratedUris; + } + } + + private static class Location { + + private static class Item { + @Nullable private final Class<?> mMaybeClass; + @Nullable private final Executable mMaybeMethod; + @Nullable private final String mExtra; + + Item(@NonNull Class<?> clazz) { + mMaybeClass = checkNotNull(clazz); + mMaybeMethod = null; + mExtra = null; + } + + Item(@NonNull Executable executable, @Nullable String extra) { + mMaybeClass = null; + mMaybeMethod = checkNotNull(executable); + mExtra = extra; + } + + @NonNull + @Override + public String toString() { + String name = mMaybeClass != null + ? "CLASS:" + mMaybeClass.getName() + : "METHOD:" + mMaybeMethod.getName() + "/" + + mMaybeMethod.getParameterCount(); + return name + Strings.nullToEmpty(mExtra); + } + } + + private final ImmutableList<Item> mComponents; + + private Location(Iterable<Item> components) { + mComponents = ImmutableList.copyOf(components); + } + + private Location(Location soFar, Item next) { + // Verify the class->method->class->method ordering. + if (!soFar.mComponents.isEmpty()) { + Item previous = soFar.getLastItem(); + if (previous.mMaybeMethod != null && next.mMaybeMethod != null) { + throw new IllegalArgumentException( + String.format("Unexpected sequence: %s ===> %s", soFar, next)); + } + } + mComponents = ImmutableList.<Item>builder().addAll(soFar.mComponents).add(next).build(); + } + + public static Location root(Class<?> clazz) { + return new Location(ImmutableList.of(new Item(clazz))); + } + + Location plus(Class<?> clazz) { + return new Location(this, new Item(clazz)); + } + + Location plus(Executable executable, String extra) { + return new Location(this, new Item(executable, extra)); + } + + public Location plus(String methodName, Class<?>... methodParameters) { + Item lastClass = getLastItem(); + try { + checkNotNull(lastClass.mMaybeClass, "Last item is not a class but %s", lastClass); + Method method = lastClass.mMaybeClass.getMethod(methodName, methodParameters); + return new Location(this, new Item(method, null)); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException( + String.format("Method %s not found in class %s", + methodName, lastClass.mMaybeClass.getName())); + } + } + + Item getLastItem() { + checkState(!mComponents.isEmpty()); + return mComponents.get(mComponents.size() - 1); + } + + @NonNull + @Override + public String toString() { + return mComponents.stream().map(Item::toString).collect(Collectors.joining(" -> ")); + } + + public long getClassOccurrenceCount(Class<?> clazz) { + return mComponents.stream().filter(c -> clazz.equals(c.mMaybeClass)).count(); + } + } + + private static class Generated<T> { + public final T value; + public final ImmutableList<Uri> includedUris; + + private Generated(T value, Iterable<Uri> includedUris) { + this.value = value; + this.includedUris = ImmutableList.copyOf(includedUris); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 7330411d1dd7..340b591e4086 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -53,6 +53,8 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -2827,6 +2829,26 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.getKeepClearAreas()); } + @Test + public void testMayImeShowOnLaunchingActivity_negativeWhenSoftInputModeHidden() { + final ActivityRecord app = createActivityRecord(mDisplayContent); + final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, app, "appWin"); + createWindow(null, TYPE_APPLICATION_STARTING, app, "startingWin"); + app.mStartingData = mock(SnapshotStartingData.class); + // Assume the app has shown IME before and warm launching with a snapshot window. + doReturn(true).when(app.mStartingData).hasImeSurface(); + + // Expect true when this IME focusable activity will show IME during launching. + assertTrue(WindowManager.LayoutParams.mayUseInputMethod(appWin.mAttrs.flags)); + assertTrue(mDisplayContent.mayImeShowOnLaunchingActivity(app)); + + // Not expect IME will be shown during launching if the app's softInputMode is hidden. + appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN; + assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app)); + appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_HIDDEN; + assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app)); + } + private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index a3a36841d807..5eebe746d64b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -26,6 +26,7 @@ import static com.android.server.wm.BLASTSyncEngine.METHOD_NONE; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE; +import static com.android.server.wm.WindowContainer.SYNC_STATE_READY; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import static org.junit.Assert.assertEquals; @@ -38,7 +39,9 @@ import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.spy; import android.platform.test.annotations.Presubmit; +import android.util.MergedConfiguration; import android.view.SurfaceControl; +import android.window.ClientWindowFrames; import androidx.test.filters.SmallTest; @@ -306,6 +309,19 @@ public class SyncEngineTests extends WindowTestsBase { assertEquals(SYNC_STATE_NONE, parentWC.mSyncState); assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState); assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState); + + // If the appearance of window won't change after reparenting, its sync state can be kept. + final WindowState w = createWindow(null, TYPE_BASE_APPLICATION, "win"); + parentWC.onRequestedOverrideConfigurationChanged(w.getConfiguration()); + w.reparent(botChildWC, POSITION_TOP); + parentWC.prepareSync(); + // Assume the window has drawn with the latest configuration. + w.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(), + new MergedConfiguration(), true /* useLatestConfig */, true /* relayoutVisible */); + assertTrue(w.onSyncFinishedDrawing()); + assertEquals(SYNC_STATE_READY, w.mSyncState); + w.reparent(topChildWC, POSITION_TOP); + assertEquals(SYNC_STATE_READY, w.mSyncState); } @Test diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 99881e194b07..fd0d5401dae3 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -89,6 +89,7 @@ public final class UsbAlsaManager { private static final int USB_VENDORID_SONY = 0x054C; private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT1 = 0x05C4; private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT2 = 0x09CC; + private static final int USB_PRODUCTID_PS5CONTROLLER = 0x0CE6; private static final int USB_DENYLIST_OUTPUT = 0x0001; private static final int USB_DENYLIST_INPUT = 0x0002; @@ -111,6 +112,9 @@ public final class UsbAlsaManager { USB_DENYLIST_OUTPUT), new DenyListEntry(USB_VENDORID_SONY, USB_PRODUCTID_PS4CONTROLLER_ZCT2, + USB_DENYLIST_OUTPUT), + new DenyListEntry(USB_VENDORID_SONY, + USB_PRODUCTID_PS5CONTROLLER, USB_DENYLIST_OUTPUT)); private static boolean isDeviceDenylisted(int vendorId, int productId, int flags) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 00d74bfe1691..f1e1a5adc2c3 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -119,6 +119,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { @GuardedBy("mLock") private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE; + @GuardedBy("mLock") + private boolean mIsAppOpPermitted = true; + SoundTriggerHelper(Context context, EventLogger eventLogger, @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider, int moduleId, @@ -323,7 +326,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { modelData.setRunInBatterySaverMode(runInBatterySaverMode); modelData.setSoundModel(soundModel); - if (isRecognitionAllowedByDeviceState(modelData)) { + if (isRecognitionAllowed(modelData)) { int startRecoResult = updateRecognitionLocked(modelData, false /* Don't notify for synchronous calls */); if (startRecoResult == SoundTrigger.STATUS_OK) { @@ -613,6 +616,16 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + public void onAppOpStateChanged(boolean isPermitted) { + synchronized (mLock) { + if (mIsAppOpPermitted == isPermitted) { + return; + } + mIsAppOpPermitted = isPermitted; + updateAllRecognitionsLocked(); + } + } + public int getGenericModelState(UUID modelId) { synchronized (mLock) { MetricsLogger.count(mContext, "sth_get_generic_model_state", 1); @@ -782,6 +795,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return event instanceof KeyphraseRecognitionEvent; } + @GuardedBy("mLock") private void onGenericRecognitionLocked(GenericRecognitionEvent event) { MetricsLogger.count(mContext, "sth_generic_recognition_event", 1); if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS @@ -789,15 +803,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } ModelData model = getModelDataForLocked(event.soundModelHandle); - if (!Objects.equals(event.getToken(), model.getToken())) { - // Stale event, do nothing - return; - } if (model == null || !model.isGenericModel()) { Slog.w(TAG, "Generic recognition event: Model does not exist for handle: " + event.soundModelHandle); return; } + if (!Objects.equals(event.getToken(), model.getToken())) { + // Stale event, do nothing + return; + } IRecognitionStatusCallback callback = model.getCallback(); if (callback == null) { @@ -866,6 +880,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + @GuardedBy("mLock") private void onResourcesAvailableLocked() { mEventLogger.enqueue(new SessionEvent(Type.RESOURCES_AVAILABLE, null)); updateAllRecognitionsLocked(); @@ -875,11 +890,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { Slog.w(TAG, "Recognition aborted"); MetricsLogger.count(mContext, "sth_recognition_aborted", 1); ModelData modelData = getModelDataForLocked(event.soundModelHandle); - if (!Objects.equals(event.getToken(), modelData.getToken())) { - // Stale event, do nothing - return; - } if (modelData != null && modelData.isModelStarted()) { + if (!Objects.equals(event.getToken(), modelData.getToken())) { + // Stale event, do nothing + return; + } modelData.setStopped(); try { IRecognitionStatusCallback callback = modelData.getCallback(); @@ -911,21 +926,21 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return keyphraseExtras[0].id; } + @GuardedBy("mLock") private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) { Slog.i(TAG, "Recognition success"); MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1); int keyphraseId = getKeyphraseIdFromEvent(event); ModelData modelData = getKeyphraseModelDataLocked(keyphraseId); - if (!Objects.equals(event.getToken(), modelData.getToken())) { - // Stale event, do nothing - return; - } if (modelData == null || !modelData.isKeyphraseModel()) { Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId); return; } - + if (!Objects.equals(event.getToken(), modelData.getToken())) { + // Stale event, do nothing + return; + } if (modelData.getCallback() == null) { Slog.w(TAG, "Received onRecognition event without callback for keyphrase model."); return; @@ -957,6 +972,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + @GuardedBy("mLock") private void updateAllRecognitionsLocked() { // updateRecognitionLocked can possibly update the list of models ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values()); @@ -965,8 +981,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + @GuardedBy("mLock") private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) { - boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model); + boolean shouldStartModel = model.isRequested() && isRecognitionAllowed(model); if (shouldStartModel == model.isModelStarted()) { // No-op. return STATUS_OK; @@ -1185,7 +1202,10 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { * @return True if recognition is allowed to run at this time. False if not. */ @GuardedBy("mLock") - private boolean isRecognitionAllowedByDeviceState(ModelData modelData) { + private boolean isRecognitionAllowed(ModelData modelData) { + if (!mIsAppOpPermitted) { + return false; + } return switch (mDeviceState) { case DISABLE -> false; case CRITICAL -> modelData.shouldRunInBatterySaverMode(); @@ -1196,6 +1216,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // A single routine that implements the start recognition logic for both generic and keyphrase // models. + @GuardedBy("mLock") private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) { IRecognitionStatusCallback callback = modelData.getCallback(); RecognitionConfig config = modelData.getRecognitionConfig(); @@ -1206,7 +1227,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return STATUS_ERROR; } - if (!isRecognitionAllowedByDeviceState(modelData)) { + if (!isRecognitionAllowed(modelData)) { // Nothing to do here. Slog.w(TAG, "startRecognition requested but not allowed."); MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index a67524887086..9fb5509141a7 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -42,6 +42,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -99,6 +100,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.ISoundTriggerService; import com.android.internal.app.ISoundTriggerSession; +import com.android.internal.util.DumpUtils; import com.android.server.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.soundtrigger.SoundTriggerEvent.ServiceEvent; @@ -110,6 +112,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.function.Consumer; import java.util.List; import java.util.Set; import java.util.Deque; @@ -235,6 +238,8 @@ public class SoundTriggerService extends SystemService { private final DeviceStateHandler mDeviceStateHandler; private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor(); private PhoneCallStateHandler mPhoneCallStateHandler; + private AppOpsManager mAppOpsManager; + private PackageManager mPackageManager; public SoundTriggerService(Context context) { super(context); @@ -257,6 +262,8 @@ public class SoundTriggerService extends SystemService { Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode()); if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) { mDbHelper = new SoundTriggerDbHelper(mContext); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + mPackageManager = mContext.getPackageManager(); final PowerManager powerManager = mContext.getSystemService(PowerManager.class); // Hook up power state listener mContext.registerReceiver( @@ -348,6 +355,44 @@ public class SoundTriggerService extends SystemService { } } + class MyAppOpsListener implements AppOpsManager.OnOpChangedListener { + private final Identity mOriginatorIdentity; + private final Consumer<Boolean> mOnOpModeChanged; + + MyAppOpsListener(Identity originatorIdentity, Consumer<Boolean> onOpModeChanged) { + mOriginatorIdentity = Objects.requireNonNull(originatorIdentity); + mOnOpModeChanged = Objects.requireNonNull(onOpModeChanged); + // Validate package name + 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); + } + } catch (PackageManager.NameNotFoundException e) { + throw new SecurityException("Package name not found: " + + mOriginatorIdentity.packageName); + } + } + + @Override + public void onOpChanged(String op, String packageName) { + if (!Objects.equals(op, AppOpsManager.OPSTR_RECORD_AUDIO)) { + return; + } + final int mode = mAppOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.uid, + mOriginatorIdentity.packageName); + mOnOpModeChanged.accept(mode == AppOpsManager.MODE_ALLOWED); + } + + void forceOpChangeRefresh() { + onOpChanged(AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.packageName); + } + } + class SoundTriggerServiceStub extends ISoundTriggerService.Stub { @Override public ISoundTriggerSession attachAsOriginator(@NonNull Identity originatorIdentity, @@ -424,6 +469,7 @@ public class SoundTriggerService extends SystemService { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; // Event loggers pw.println("##Service-Wide logs:"); mServiceEventLogger.dump(pw, /* indent = */ " "); @@ -461,6 +507,7 @@ public class SoundTriggerService extends SystemService { private final Object mCallbacksLock = new Object(); private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>(); private final EventLogger mEventLogger; + private final MyAppOpsListener mAppOpsListener; SoundTriggerSessionStub(@NonNull IBinder client, SoundTriggerHelper soundTriggerHelper, EventLogger eventLogger) { @@ -477,6 +524,12 @@ public class SoundTriggerService extends SystemService { } mListener = (SoundTriggerDeviceState state) -> mSoundTriggerHelper.onDeviceStateChanged(state); + mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity, + mSoundTriggerHelper::onAppOpStateChanged); + mAppOpsListener.forceOpChangeRefresh(); + mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO, + mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES, + mAppOpsListener); mDeviceStateHandler.registerListener(mListener); } @@ -928,6 +981,9 @@ public class SoundTriggerService extends SystemService { } private void detach() { + if (mAppOpsListener != null) { + mAppOpsManager.stopWatchingMode(mAppOpsListener); + } mDeviceStateHandler.unregisterListener(mListener); mSoundTriggerHelper.detach(); detachSessionLogger(mEventLogger); @@ -943,9 +999,8 @@ public class SoundTriggerService extends SystemService { } private void enforceDetectionPermissions(ComponentName detectionService) { - PackageManager packageManager = mContext.getPackageManager(); String packageName = detectionService.getPackageName(); - if (packageManager.checkPermission( + if (mPackageManager.checkPermission( Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException(detectionService.getPackageName() + " does not have" @@ -1633,6 +1688,7 @@ public class SoundTriggerService extends SystemService { private final EventLogger mEventLogger; private final Identity mOriginatorIdentity; private final @NonNull DeviceStateListener mListener; + private final MyAppOpsListener mAppOpsListener; private final SparseArray<UUID> mModelUuid = new SparseArray<>(1); @@ -1653,6 +1709,12 @@ public class SoundTriggerService extends SystemService { } mListener = (SoundTriggerDeviceState state) -> mSoundTriggerHelper.onDeviceStateChanged(state); + mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity, + mSoundTriggerHelper::onAppOpStateChanged); + mAppOpsListener.forceOpChangeRefresh(); + mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO, + mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES, + mAppOpsListener); mDeviceStateHandler.registerListener(mListener); } @@ -1720,6 +1782,9 @@ public class SoundTriggerService extends SystemService { } private void detachInternal() { + if (mAppOpsListener != null) { + mAppOpsManager.stopWatchingMode(mAppOpsListener); + } mEventLogger.enqueue(new SessionEvent(Type.DETACH, null)); detachSessionLogger(mEventLogger); mDeviceStateHandler.unregisterListener(mListener); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 31fab89d1d4e..7ec2d9fd7b23 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -265,13 +265,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware /** Model is loaded, recognition is active. */ ACTIVE, /** - * Model is active as far as the client is concerned, but loaded as far as the - * layers are concerned. This condition occurs when a recognition event that indicates - * the recognition for this model arrived from the underlying layer, but had not been - * delivered to the caller (most commonly, for permission reasons). - */ - INTERCEPTED, - /** * Model has been preemptively unloaded by the HAL. */ PREEMPTED, @@ -483,18 +476,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware throw new IllegalStateException("Invalid handle: " + modelHandle); } // stopRecognition is idempotent - no need to check model state. - - // From here on, every exception isn't client's fault. - try { - // If the activity state is INTERCEPTED, we skip delegating the command, but - // still consider the call valid. - if (modelState.activityState == ModelState.Activity.INTERCEPTED) { - modelState.activityState = ModelState.Activity.LOADED; - return; - } - } catch (Exception e) { - throw handleException(e); - } } // Calling the delegate's stop must be done without the lock. @@ -518,27 +499,6 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } } - private void restartIfIntercepted(int modelHandle) { - synchronized (SoundTriggerMiddlewareValidation.this) { - // State validation. - if (mState == ModuleStatus.DETACHED) { - return; - } - ModelState modelState = mLoadedModels.get(modelHandle); - if (modelState == null - || modelState.activityState != ModelState.Activity.INTERCEPTED) { - return; - } - try { - mDelegate.startRecognition(modelHandle, modelState.config); - modelState.activityState = ModelState.Activity.ACTIVE; - Log.i(TAG, "Restarted intercepted model " + modelHandle); - } catch (Exception e) { - Log.i(TAG, "Failed to restart intercepted model " + modelHandle, e); - } - } - } - @Override public void forceRecognitionEvent(int modelHandle) { // Input validation (always valid). @@ -742,18 +702,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware mCallback.onRecognition(modelHandle, event, captureSession); } catch (Exception e) { Log.w(TAG, "Client callback exception.", e); - synchronized (SoundTriggerMiddlewareValidation.this) { - ModelState modelState = mLoadedModels.get(modelHandle); - if (event.recognitionEvent.status != RecognitionStatus.FORCED) { - modelState.activityState = ModelState.Activity.INTERCEPTED; - // If we failed to deliver an actual event to the client, they would - // never know to restart it whenever circumstances change. Thus, we - // restart it here. We do this from a separate thread to avoid any - // race conditions. - new Thread(() -> restartIfIntercepted(modelHandle)).start(); - } - } - } + } } @Override @@ -771,18 +720,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware mCallback.onPhraseRecognition(modelHandle, event, captureSession); } catch (Exception e) { Log.w(TAG, "Client callback exception.", e); - synchronized (SoundTriggerMiddlewareValidation.this) { - ModelState modelState = mLoadedModels.get(modelHandle); - if (!event.phraseRecognitionEvent.common.recognitionStillActive) { - modelState.activityState = ModelState.Activity.INTERCEPTED; - // If we failed to deliver an actual event to the client, they would - // never know to restart it whenever circumstances change. Thus, we - // restart it here. We do this from a separate thread to avoid any - // race conditions. - new Thread(() -> restartIfIntercepted(modelHandle)).start(); - } - } - } + } } @Override 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 083211c29283..f70268e1848a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -487,10 +487,11 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo if (mRecognitionToken == null) { return; } + event.token = mRecognitionToken; if (!event.recognitionEvent.recognitionStillActive) { setState(ModelState.LOADED); + mRecognitionToken = null; } - event.token = mRecognitionToken; callback = mCallback; } // The callback must be invoked outside of the lock. @@ -512,10 +513,11 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo if (mRecognitionToken == null) { return; } + event.token = mRecognitionToken; if (!event.phraseRecognitionEvent.common.recognitionStillActive) { setState(ModelState.LOADED); + mRecognitionToken = null; } - event.token = mRecognitionToken; callback = mCallback; } // The callback must be invoked outside of the lock. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c4a501d336bc..2a6099a18fab 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13297,6 +13297,29 @@ public class TelephonyManager { } /** + * Test API to verify carrier restriction status allow list i.e. + * packages/services/Telephony/assets/CarrierRestrictionOperatorDetails.json. + * + * @param pkgName : packaga name of the entry to verify + * @param carrierId : carrier Id of the entry + * @return {@code List<String>} : list of registered shaIds + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public List<String> getShaIdFromAllowList(String pkgName, int carrierId) { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getShaIdFromAllowList(pkgName, carrierId); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getShaIdFromAllowList: RemoteException = " + ex); + throw ex.rethrowAsRuntimeException(); + } + return Collections.EMPTY_LIST; + } + + /** * Used to enable or disable carrier data by the system based on carrier signalling or * carrier privileged apps. Different from {@link #setDataEnabled(boolean)} which is linked to * user settings, carrier data on/off won't affect user settings but will bypass the diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 21aad73ce005..23f4217caf9d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3037,4 +3037,11 @@ interface ITelephony { * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. */ boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis); + + /** + * Test method to confirm the file contents are not altered. + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(" + + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)") + List<String> getShaIdFromAllowList(String pkgName, int carrierId); } |