diff options
218 files changed, 5757 insertions, 1996 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..e0cc143f74ee 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -4124,6 +4124,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; 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/ProcessMemoryState.java b/core/java/android/app/ProcessMemoryState.java index 2c58603ecdb9..c4caa4512acc 100644 --- a/core/java/android/app/ProcessMemoryState.java +++ b/core/java/android/app/ProcessMemoryState.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.IntDef; import android.os.Parcel; import android.os.Parcelable; @@ -24,19 +25,132 @@ import android.os.Parcelable; * {@hide} */ public final class ProcessMemoryState implements Parcelable { + /** + * The type of the component this process is hosting; + * this means not hosting any components (cached). + */ + public static final int HOSTING_COMPONENT_TYPE_EMPTY = + AppProtoEnums.HOSTING_COMPONENT_TYPE_EMPTY; + + /** + * The type of the component this process is hosting; + * this means it's a system process. + */ + public static final int HOSTING_COMPONENT_TYPE_SYSTEM = + AppProtoEnums.HOSTING_COMPONENT_TYPE_SYSTEM; + + /** + * The type of the component this process is hosting; + * this means it's a persistent process. + */ + public static final int HOSTING_COMPONENT_TYPE_PERSISTENT = + AppProtoEnums.HOSTING_COMPONENT_TYPE_PERSISTENT; + + /** + * The type of the component this process is hosting; + * this means it's hosting a backup/restore agent. + */ + public static final int HOSTING_COMPONENT_TYPE_BACKUP = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BACKUP; + + /** + * The type of the component this process is hosting; + * this means it's hosting an instrumentation. + */ + public static final int HOSTING_COMPONENT_TYPE_INSTRUMENTATION = + AppProtoEnums.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; + + /** + * The type of the component this process is hosting; + * this means it's hosting an activity. + */ + public static final int HOSTING_COMPONENT_TYPE_ACTIVITY = + AppProtoEnums.HOSTING_COMPONENT_TYPE_ACTIVITY; + + /** + * The type of the component this process is hosting; + * this means it's hosting a broadcast receiver. + */ + public static final int HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; + + /** + * The type of the component this process is hosting; + * this means it's hosting a content provider. + */ + public static final int HOSTING_COMPONENT_TYPE_PROVIDER = + AppProtoEnums.HOSTING_COMPONENT_TYPE_PROVIDER; + + /** + * The type of the component this process is hosting; + * this means it's hosting a started service. + */ + public static final int HOSTING_COMPONENT_TYPE_STARTED_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; + + /** + * The type of the component this process is hosting; + * this means it's hosting a foreground service. + */ + public static final int HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; + + /** + * The type of the component this process is hosting; + * this means it's being bound via a service binding. + */ + public static final int HOSTING_COMPONENT_TYPE_BOUND_SERVICE = + AppProtoEnums.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; + + /** + * The type of the component this process is hosting. + * @hide + */ + @IntDef(flag = true, prefix = { "HOSTING_COMPONENT_TYPE_" }, value = { + HOSTING_COMPONENT_TYPE_EMPTY, + HOSTING_COMPONENT_TYPE_SYSTEM, + HOSTING_COMPONENT_TYPE_PERSISTENT, + HOSTING_COMPONENT_TYPE_BACKUP, + HOSTING_COMPONENT_TYPE_INSTRUMENTATION, + HOSTING_COMPONENT_TYPE_ACTIVITY, + HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER, + HOSTING_COMPONENT_TYPE_PROVIDER, + HOSTING_COMPONENT_TYPE_STARTED_SERVICE, + HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE, + HOSTING_COMPONENT_TYPE_BOUND_SERVICE, + }) + public @interface HostingComponentType {} + public final int uid; public final int pid; public final String processName; public final int oomScore; public final boolean hasForegroundServices; + /** + * The types of the components this process is hosting at the moment this snapshot is taken. + * + * Its value is the combination of {@link HostingComponentType}. + */ + public final int mHostingComponentTypes; + + /** + * The historical types of the components this process is or was hosting since it's born. + * + * Its value is the combination of {@link HostingComponentType}. + */ + public final int mHistoricalHostingComponentTypes; + public ProcessMemoryState(int uid, int pid, String processName, int oomScore, - boolean hasForegroundServices) { + boolean hasForegroundServices, int hostingComponentTypes, + int historicalHostingComponentTypes) { this.uid = uid; this.pid = pid; this.processName = processName; this.oomScore = oomScore; this.hasForegroundServices = hasForegroundServices; + this.mHostingComponentTypes = hostingComponentTypes; + this.mHistoricalHostingComponentTypes = historicalHostingComponentTypes; } private ProcessMemoryState(Parcel in) { @@ -45,6 +159,8 @@ public final class ProcessMemoryState implements Parcelable { processName = in.readString(); oomScore = in.readInt(); hasForegroundServices = in.readInt() == 1; + mHostingComponentTypes = in.readInt(); + mHistoricalHostingComponentTypes = in.readInt(); } public static final @android.annotation.NonNull Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() { @@ -71,5 +187,7 @@ public final class ProcessMemoryState implements Parcelable { parcel.writeString(processName); parcel.writeInt(oomScore); parcel.writeInt(hasForegroundServices ? 1 : 0); + parcel.writeInt(mHostingComponentTypes); + parcel.writeInt(mHistoricalHostingComponentTypes); } } 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 56f6f8206d30..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. @@ -2565,9 +2580,9 @@ public class PackageInstaller { * Sets the state of permissions for the package at installation. * <p/> * Granting any runtime permissions require the - * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be - * held by the caller. Revoking runtime permissions is not allowed, even during app update - * sessions. + * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS + * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime + * permissions is not allowed, even during app update sessions. * <p/> * Holders without the permission are allowed to change the following special permissions: * <p/> 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/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/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/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 18405676dbd8..eec05b94734b 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()); } @@ -726,9 +725,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 +741,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 +762,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/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/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/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/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/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/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/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/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 b81339e85bcc..c64ebda2eb5c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -487,6 +487,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) is CreatePublicKeyCredentialRequest -> { newRequestDisplayInfoFromPasskeyJson( @@ -497,6 +498,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } is CreateCustomCredentialRequest -> { @@ -515,6 +517,7 @@ class CreateFlowUtils { createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, appPreferredDefaultProviderId = appPreferredDefaultProviderId, userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(), + isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed, ) } else -> null @@ -602,7 +605,7 @@ class CreateFlowUtils { ) } - private fun toCreateScreenState( + fun toCreateScreenState( createOptionSize: Int, isOnPasskeyIntroStateAlready: Boolean, requestDisplayInfo: RequestDisplayInfo, @@ -661,8 +664,14 @@ class CreateFlowUtils { passwordCount = createEntry.getPasswordCredentialCount(), passkeyCount = createEntry.getPublicKeyCredentialCount(), totalCredentialCount = createEntry.getTotalCredentialCount(), - lastUsedTime = createEntry.lastUsedTime, - footerDescription = createEntry.description?.toString() + lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN, + 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 +703,7 @@ class CreateFlowUtils { preferImmediatelyAvailableCredentials: Boolean, appPreferredDefaultProviderId: String?, userSetDefaultProviderIds: Set<String>, + isAutoSelectRequest: Boolean ): RequestDisplayInfo? { val json = JSONObject(requestJson) var passkeyUsername = "" @@ -716,6 +726,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/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 fe1ce1b60413..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 && @@ -72,8 +87,9 @@ class CreateOptionInfo( val passwordCount: Int?, val passkeyCount: Int?, val totalCredentialCount: Int?, - val lastUsedTime: Instant?, + 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..4183a5211556 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, ) } 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/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/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/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index b15378570358..1f75e81c201f 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -16,6 +16,11 @@ package com.android.keyguard; +import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED; +import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED; +import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE; +import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO; + import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -32,6 +37,7 @@ import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.keyguard.logging.CarrierTextManagerLogger; import com.android.settingslib.WirelessUtils; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; @@ -40,6 +46,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository; import com.android.systemui.telephony.TelephonyListenerManager; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -68,6 +75,7 @@ public class CarrierTextManager { private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); @VisibleForTesting protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final CarrierTextManagerLogger mLogger; private final WifiRepository mWifiRepository; private final boolean[] mSimErrorState; private final int mSimSlotsNumber; @@ -97,19 +105,13 @@ public class CarrierTextManager { protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() { @Override public void onRefreshCarrierInfo() { - if (DEBUG) { - Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: " - + Boolean.toString(mTelephonyCapable)); - } + mLogger.logUpdateCarrierTextForReason(REASON_REFRESH_CARRIER_INFO); updateCarrierText(); } @Override public void onTelephonyCapable(boolean capable) { - if (DEBUG) { - Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: " - + Boolean.toString(capable)); - } + mLogger.logUpdateCarrierTextForReason(REASON_ON_TELEPHONY_CAPABLE); mTelephonyCapable = capable; updateCarrierText(); } @@ -121,7 +123,7 @@ public class CarrierTextManager { return; } - if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState)); + mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED); if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) { mSimErrorState[slotId] = true; updateCarrierText(); @@ -137,6 +139,7 @@ public class CarrierTextManager { @Override public void onActiveDataSubscriptionIdChanged(int subId) { if (mNetworkSupported.get() && mCarrierTextCallback != null) { + mLogger.logUpdateCarrierTextForReason(REASON_ACTIVE_DATA_SUB_CHANGED); updateCarrierText(); } } @@ -175,7 +178,9 @@ public class CarrierTextManager { WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor, @Background Executor bgExecutor, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + CarrierTextManagerLogger logger) { + mContext = context; mIsEmergencyCallCapable = telephonyManager.isVoiceCapable(); @@ -191,6 +196,7 @@ public class CarrierTextManager { mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLogger = logger; mBgExecutor.execute(() -> { boolean supported = mContext.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TELEPHONY); @@ -315,7 +321,7 @@ public class CarrierTextManager { subOrderBySlot[i] = -1; } final CharSequence[] carrierNames = new CharSequence[numSubs]; - if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs); + mLogger.logUpdate(numSubs); for (int i = 0; i < numSubs; i++) { int subId = subs.get(i).getSubscriptionId(); @@ -325,9 +331,7 @@ public class CarrierTextManager { int simState = mKeyguardUpdateMonitor.getSimState(subId); CharSequence carrierName = subs.get(i).getCarrierName(); CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName); - if (DEBUG) { - Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName); - } + mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName)); if (carrierTextForSimState != null) { allSimsMissing = false; carrierNames[i] = carrierTextForSimState; @@ -340,9 +344,7 @@ public class CarrierTextManager { // Wi-Fi is disassociated or disabled if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN || mWifiRepository.isWifiConnectedWithValidSsid()) { - if (DEBUG) { - Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); - } + mLogger.logUpdateWfcCheck(); anySimReadyAndInService = true; } } @@ -379,7 +381,7 @@ public class CarrierTextManager { if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) { plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN); } - if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn); + mLogger.logUpdateFromStickyBroadcast(plmn, spn); if (Objects.equals(plmn, spn)) { text = plmn; } else { @@ -409,6 +411,7 @@ public class CarrierTextManager { !allSimsMissing, subsIds, airplaneMode); + mLogger.logCallbackSentFromUpdate(info); postToCallback(info); Trace.endSection(); } @@ -645,6 +648,7 @@ public class CarrierTextManager { private final Executor mMainExecutor; private final Executor mBgExecutor; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final CarrierTextManagerLogger mLogger; private boolean mShowAirplaneMode; private boolean mShowMissingSim; @@ -658,7 +662,8 @@ public class CarrierTextManager { WakefulnessLifecycle wakefulnessLifecycle, @Main Executor mainExecutor, @Background Executor bgExecutor, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + CarrierTextManagerLogger logger) { mContext = context; mSeparator = resources.getString( com.android.internal.R.string.kg_text_message_separator); @@ -669,6 +674,7 @@ public class CarrierTextManager { mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLogger = logger; } /** */ @@ -688,7 +694,7 @@ public class CarrierTextManager { return new CarrierTextManager( mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, - mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor); + mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger); } } /** @@ -716,6 +722,17 @@ public class CarrierTextManager { this.subscriptionIds = subscriptionIds; this.airplaneMode = airplaneMode; } + + @Override + public String toString() { + return "CarrierTextCallbackInfo{" + + "carrierText=" + carrierText + + ", listOfCarriers=" + Arrays.toString(listOfCarriers) + + ", anySimReady=" + anySimReady + + ", subscriptionIds=" + Arrays.toString(subscriptionIds) + + ", airplaneMode=" + airplaneMode + + '}'; + } } /** diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt new file mode 100644 index 000000000000..3dc787cab0b2 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.keyguard.logging + +import androidx.annotation.IntDef +import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.CarrierTextManagerLog +import javax.inject.Inject + +/** Logger adapter for [CarrierTextManager] to add detailed messages in a [LogBuffer] */ +@SysUISingleton +class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) { + /** + * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText + */ + fun logUpdate(numSubs: Int) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { int1 = numSubs }, + { "updateCarrierText: numSubs=$int1" }, + ) + } + + fun logUpdateLoopStart(sub: Int, simState: Int, carrierName: String) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + int1 = sub + int2 = simState + str1 = carrierName + }, + { "┣ updateCarrierText: updating sub=$int1 simState=$int2 carrierName=$str1" }, + ) + } + + fun logUpdateWfcCheck() { + buffer.log( + TAG, + LogLevel.VERBOSE, + {}, + { "┣ updateCarrierText: found WFC state" }, + ) + } + + fun logUpdateFromStickyBroadcast(plmn: String, spn: String) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = plmn + str2 = spn + }, + { "┣ updateCarrierText: getting PLMN/SPN sticky brdcst. plmn=$str1, spn=$str1" }, + ) + } + + /** De-structures the info object so that we don't have to generate new strings */ + fun logCallbackSentFromUpdate(info: CarrierTextCallbackInfo) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = "${info.carrierText}" + bool1 = info.anySimReady + bool2 = info.airplaneMode + }, + { + "┗ updateCarrierText: " + + "result=(carrierText=$str1, anySimReady=$bool1, airplaneMode=$bool2)" + }, + ) + } + + /** + * Used to log the starting point for _why_ the carrier text is updating. In order to keep us + * from holding on to too many objects, we'll just use simple ints for reasons here + */ + fun logUpdateCarrierTextForReason(@CarrierTextRefreshReason reason: Int) { + buffer.log( + TAG, + LogLevel.DEBUG, + { int1 = reason }, + { "refreshing carrier info for reason: ${reason.reasonMessage()}" } + ) + } + + companion object { + const val REASON_REFRESH_CARRIER_INFO = 1 + const val REASON_ON_TELEPHONY_CAPABLE = 2 + const val REASON_ON_SIM_STATE_CHANGED = 3 + const val REASON_ACTIVE_DATA_SUB_CHANGED = 4 + + @Retention(AnnotationRetention.SOURCE) + @IntDef( + value = + [ + REASON_REFRESH_CARRIER_INFO, + REASON_ON_TELEPHONY_CAPABLE, + REASON_ON_SIM_STATE_CHANGED, + REASON_ACTIVE_DATA_SUB_CHANGED, + ] + ) + annotation class CarrierTextRefreshReason + + private fun @receiver:CarrierTextRefreshReason Int.reasonMessage() = + when (this) { + REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO" + REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE" + REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED" + REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED" + else -> "unknown" + } + } +} + +private const val TAG = "CarrierTextManagerLog" diff --git a/packages/SystemUI/src/com/android/systemui/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/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 85788456d1cb..70c39df2a610 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -31,9 +31,6 @@ import android.os.Process; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.util.ArrayMap; -import android.util.Dumpable; -import android.util.DumpableContainer; import android.util.Log; import android.util.TimingsTraceLog; import android.view.SurfaceControl; @@ -57,18 +54,12 @@ import javax.inject.Provider; * Application class for SystemUI. */ public class SystemUIApplication extends Application implements - SystemUIAppComponentFactory.ContextInitializer, DumpableContainer { + SystemUIAppComponentFactory.ContextInitializer { public static final String TAG = "SystemUIService"; private static final boolean DEBUG = false; private BootCompleteCacheImpl mBootCompleteCache; - private DumpManager mDumpManager; - - /** - * Map of dumpables added externally. - */ - private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>(); /** * Hold a reference on the stuff we start. @@ -233,7 +224,7 @@ public class SystemUIApplication extends Application implements } } - mDumpManager = mSysUIComponent.createDumpManager(); + DumpManager dumpManager = mSysUIComponent.createDumpManager(); Log.v(TAG, "Starting SystemUI services for user " + Process.myUserHandle().getIdentifier() + "."); @@ -267,7 +258,7 @@ public class SystemUIApplication extends Application implements notifyBootCompleted(mServices[i]); } - mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); + dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); } mSysUIComponent.getInitController().executePostInitTasks(); log.traceEnd(); @@ -342,36 +333,6 @@ public class SystemUIApplication extends Application implements return startable; } - // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest... - @Override - public boolean addDumpable(Dumpable dumpable) { - String name = dumpable.getDumpableName(); - if (mDumpables.containsKey(name)) { - // This is normal because SystemUIApplication is an application context that is shared - // among multiple components - if (DEBUG) { - Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable" - + " with that name (" + name + "): " + mDumpables.get(name)); - } - return false; - } - if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable); - mDumpables.put(name, dumpable); - - // TODO(b/217567642): replace com.android.systemui.dump.Dumpable by - // com.android.util.Dumpable and get rid of the intermediate lambda - mDumpManager.registerDumpable(dumpable.getDumpableName(), dumpable::dump); - return true; - } - - // TODO(b/217567642): implement - @Override - public boolean removeDumpable(Dumpable dumpable) { - Log.w(TAG, "removeDumpable(" + dumpable + "): not implemented"); - - return false; - } - @Override public void onConfigurationChanged(Configuration newConfig) { if (mServicesStarted) { 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/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..6967e6c23f35 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -710,6 +710,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/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/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/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/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/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/log/dagger/CarrierTextManagerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt new file mode 100644 index 000000000000..62b80b20a673 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt @@ -0,0 +1,9 @@ +package com.android.systemui.log.dagger + +import javax.inject.Qualifier + +/** A [LogBuffer] for detailed carrier text logs. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class CarrierTextManagerLog diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 9be18ace79fa..66c3c02df1fc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -373,6 +373,16 @@ public class LogModule { } /** + * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}. + */ + @Provides + @SysUISingleton + @CarrierTextManagerLog + public static LogBuffer provideCarrierTextManagerLog(LogBufferFactory factory) { + return factory.create("CarrierTextManagerLog", 100); + } + + /** * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}. */ @Provides 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/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/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/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index eb20bba0d21f..991ff56e683c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -21,6 +21,7 @@ import androidx.annotation.VisibleForTesting import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController @@ -62,6 +63,7 @@ import kotlinx.coroutines.flow.stateIn */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton class MobileRepositorySwitcher @Inject constructor( 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/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt index b1296179d7f7..e96288ab9ef9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository import android.os.Bundle import androidx.annotation.VisibleForTesting import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController @@ -54,6 +55,7 @@ import kotlinx.coroutines.flow.stateIn */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton class WifiRepositorySwitcher @Inject constructor( 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/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/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index 5557efa97e3e..0b9ba78229e9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -47,6 +47,7 @@ import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.text.TextUtils; +import com.android.keyguard.logging.CarrierTextManagerLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -96,6 +97,8 @@ public class CarrierTextManagerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock + private CarrierTextManagerLogger mLogger; + @Mock private PackageManager mPackageManager; @Mock private TelephonyManager mTelephonyManager; @@ -144,7 +147,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { mCarrierTextManager = new CarrierTextManager.Builder( mContext, mContext.getResources(), mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, - mBgExecutor, mKeyguardUpdateMonitor) + mBgExecutor, mKeyguardUpdateMonitor, mLogger) .setShowAirplaneMode(true) .setShowMissingSim(true) .build(); 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/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/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/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/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/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/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/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/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/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..fee20c89c106 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -470,12 +470,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 +569,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 +617,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/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/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/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5d3bb31bc31f..9514572e3b01 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -53,6 +53,10 @@ import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BACKUP; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES; import static android.content.pm.PackageManager.MATCH_ALL; @@ -158,10 +162,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.MemoryStatUtil.hasMemcg; import static com.android.server.am.ProcessList.ProcStartHandler; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BACKUP; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_INSTRUMENTATION; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PERSISTENT; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_SYSTEM; import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND; @@ -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); } @@ -17687,7 +17696,9 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessRecord r = mPidsSelfLocked.valueAt(i); processMemoryStates.add(new ProcessMemoryState( r.uid, r.getPid(), r.processName, r.mState.getCurAdj(), - r.mServices.hasForegroundServices())); + r.mServices.hasForegroundServices(), + r.mProfile.getCurrentHostingComponentTypes(), + r.mProfile.getHistoricalHostingComponentTypes())); } } return processMemoryStates; @@ -18964,6 +18975,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..59aab4f56404 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; @@ -499,6 +505,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 +546,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 +561,7 @@ class BroadcastProcessQueue { mActive = null; mActiveIndex = 0; mActiveCountSinceIdle = 0; + mActiveAssumedDeliveryCountSinceIdle = 0; mActiveViaColdStart = false; invalidateRunnableAt(); } @@ -748,7 +765,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 +778,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 +788,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 +879,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 +901,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; @@ -1309,6 +1386,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..10a7c12e206b 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; @@ -446,43 +447,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 +501,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 +547,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 +685,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 +706,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 +739,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunningColdStart = null; finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "startProcessLocked failed"); - return; + return true; } + return false; } /** @@ -754,38 +752,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 +832,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 +901,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (assumeDelivered) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "assuming delivered"); + return false; } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); @@ -908,17 +912,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 +997,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, "deliveryTimeoutHardLocked"); + demoteFromRunningLocked(queue); } @Override @@ -1015,7 +1024,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 +1036,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 +1108,63 @@ 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) { + 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; } /** @@ -1376,6 +1443,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 +1460,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)); - } - } } } 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/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 6015e5f02221..e744eee8b485 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PROVIDER; import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; @@ -34,7 +35,6 @@ import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_E import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerService.TAG_MU; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER; import android.annotation.Nullable; import android.annotation.UserIdInt; diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java index 4c15308a574e..5ad49a47a012 100644 --- a/services/core/java/com/android/server/am/ProcessProfileRecord.java +++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java @@ -17,9 +17,10 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_EMPTY; -import android.annotation.IntDef; import android.app.IApplicationThread; +import android.app.ProcessMemoryState.HostingComponentType; import android.content.pm.ApplicationInfo; import android.os.Debug; import android.os.SystemClock; @@ -42,88 +43,6 @@ import java.util.concurrent.atomic.AtomicLong; * Profiling info of the process, such as PSS, cpu, etc. */ final class ProcessProfileRecord { - // Keep below types in sync with the HostingComponentType in the atoms.proto. - /** - * The type of the component this process is hosting; - * this means not hosting any components (cached). - */ - static final int HOSTING_COMPONENT_TYPE_EMPTY = 0x0; - - /** - * The type of the component this process is hosting; - * this means it's a system process. - */ - static final int HOSTING_COMPONENT_TYPE_SYSTEM = 0x00000001; - - /** - * The type of the component this process is hosting; - * this means it's a persistent process. - */ - static final int HOSTING_COMPONENT_TYPE_PERSISTENT = 0x00000002; - - /** - * The type of the component this process is hosting; - * this means it's hosting a backup/restore agent. - */ - static final int HOSTING_COMPONENT_TYPE_BACKUP = 0x00000004; - - /** - * The type of the component this process is hosting; - * this means it's hosting an instrumentation. - */ - static final int HOSTING_COMPONENT_TYPE_INSTRUMENTATION = 0x00000008; - - /** - * The type of the component this process is hosting; - * this means it's hosting an activity. - */ - static final int HOSTING_COMPONENT_TYPE_ACTIVITY = 0x00000010; - - /** - * The type of the component this process is hosting; - * this means it's hosting a broadcast receiver. - */ - static final int HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER = 0x00000020; - - /** - * The type of the component this process is hosting; - * this means it's hosting a content provider. - */ - static final int HOSTING_COMPONENT_TYPE_PROVIDER = 0x00000040; - - /** - * The type of the component this process is hosting; - * this means it's hosting a started service. - */ - static final int HOSTING_COMPONENT_TYPE_STARTED_SERVICE = 0x00000080; - - /** - * The type of the component this process is hosting; - * this means it's hosting a foreground service. - */ - static final int HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE = 0x00000100; - - /** - * The type of the component this process is hosting; - * this means it's being bound via a service binding. - */ - static final int HOSTING_COMPONENT_TYPE_BOUND_SERVICE = 0x00000200; - - @IntDef(flag = true, prefix = { "HOSTING_COMPONENT_TYPE_" }, value = { - HOSTING_COMPONENT_TYPE_EMPTY, - HOSTING_COMPONENT_TYPE_SYSTEM, - HOSTING_COMPONENT_TYPE_PERSISTENT, - HOSTING_COMPONENT_TYPE_BACKUP, - HOSTING_COMPONENT_TYPE_INSTRUMENTATION, - HOSTING_COMPONENT_TYPE_ACTIVITY, - HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER, - HOSTING_COMPONENT_TYPE_PROVIDER, - HOSTING_COMPONENT_TYPE_STARTED_SERVICE, - HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE, - HOSTING_COMPONENT_TYPE_BOUND_SERVICE, - }) - @interface HostingComponentType {} - final ProcessRecord mApp; private final ActivityManagerService mService; diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 53fa4f1b2ac2..81d0b6ac700b 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -16,8 +16,8 @@ package com.android.server.am; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE; import android.app.ActivityManager; import android.content.Context; diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index ab71acd5f21d..db341d253818 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -19,11 +19,11 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BROADCAST_RECEIVER; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_STARTED_SERVICE; import static com.android.server.am.ProcessRecord.TAG; import android.annotation.ElapsedRealtimeLong; diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 6551db9ad783..b22ece30c386 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import static android.os.PowerExemptionManager.REASON_DENIED; import static android.os.Process.INVALID_UID; @@ -25,7 +26,6 @@ import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_BOUND_SERVICE; import android.annotation.NonNull; import android.annotation.Nullable; 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/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/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/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 6eded1a14dbf..e82521584731 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -18,6 +18,7 @@ package com.android.server.stats.pull; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppProtoEnums.HOSTING_COMPONENT_TYPE_EMPTY; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH; @@ -2350,7 +2351,8 @@ public class StatsPullAtomService extends SystemService { snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices, - snapshot.rssShmemKilobytes)); + snapshot.rssShmemKilobytes, managedProcess.mHostingComponentTypes, + managedProcess.mHistoricalHostingComponentTypes)); } // Complement the data with native system processes. Given these measurements can be taken // in response to LMKs happening, we want to first collect the managed app stats (to @@ -2370,7 +2372,10 @@ public class StatsPullAtomService extends SystemService { snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, gpuMemPerPid.get(pid), false /* has_foreground_services */, - snapshot.rssShmemKilobytes)); + snapshot.rssShmemKilobytes, + // Native processes don't really have a hosting component type. + HOSTING_COMPONENT_TYPE_EMPTY, + HOSTING_COMPONENT_TYPE_EMPTY)); } return StatsManager.PULL_SUCCESS; } 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/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 8baf048980ed..f253fb0c7271 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3922,15 +3922,17 @@ public class WindowManagerService extends IWindowManager.Stub /** * Returns the touch mode state for the display id passed as argument. + * + * This method will return the default touch mode state (represented by + * {@code com.android.internal.R.bool.config_defaultInTouchMode}) if the display passed as + * argument is no longer registered in {@RootWindowContainer}). */ @Override // Binder call public boolean isInTouchMode(int displayId) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent == null) { - throw new IllegalStateException("Failed to retrieve the touch mode state for" - + "display {" + displayId + "}: display is not registered in " - + "WindowRootContainer"); + return mContext.getResources().getBoolean(R.bool.config_defaultInTouchMode); } return displayContent.isInTouchMode(); } 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/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/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index acfea85d60a2..5d3b91368dcb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -769,7 +769,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 +814,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..3a8d2c92eaff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1890,6 +1890,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. */ 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..5f2db795f8bc 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); @@ -774,15 +774,21 @@ public class MagnificationControllerTest { verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(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 +912,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 @@ -1106,48 +1119,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/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/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/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 58bf184994e4..d3f68185a269 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -67,7 +67,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.ActivityInfo; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.hardware.display.VirtualDisplay; @@ -515,12 +514,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -539,12 +534,8 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() { - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); @@ -563,6 +554,9 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnTouchMode = @@ -570,17 +564,10 @@ public class WindowManagerServiceTests extends WindowTestsBase { final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(3); final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream() - .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) - .count(); + .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0) + .count(); assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -598,18 +585,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() { + // Enable global touch mode + mWm.mPerDisplayFocusEnabled = true; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Disable global touch mode (config_perDisplayFocusEnabled set to true) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(true).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -628,18 +611,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { @Test public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() { + // Disable global touch mode + mWm.mPerDisplayFocusEnabled = false; + // Create one extra display final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true); final int numberOfDisplays = mWm.mRoot.mChildren.size(); assertThat(numberOfDisplays).isAtLeast(2); - // Enable global touch mode (config_perDisplayFocusEnabled set to false) - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(false).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - // Get current touch mode state and setup WMS to run setInTouchMode boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY); int callingPid = Binder.getCallingPid(); @@ -667,19 +646,14 @@ public class WindowManagerServiceTests extends WindowTestsBase { } private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) { + // Set global touch mode with the value passed as argument. + mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled; + // Create a couple of extra displays. // setInTouchModeOnAllDisplays should ignore the ownFocus setting. final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false); final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true); - // Enable or disable global touch mode (config_perDisplayFocusEnabled setting). - // setInTouchModeOnAllDisplays should ignore this value. - Resources mockResources = mock(Resources.class); - spyOn(mContext); - when(mContext.getResources()).thenReturn(mockResources); - doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean( - com.android.internal.R.bool.config_perDisplayFocusEnabled); - int callingPid = Binder.getCallingPid(); int callingUid = Binder.getCallingUid(); doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index a4cad5e24dc1..863523f826b2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -197,6 +197,8 @@ class WindowTestsBase extends SystemServiceTestsBase { */ private static boolean sOverridesCheckedTestDisplay; + private boolean mOriginalPerDisplayFocusEnabled; + @BeforeClass public static void setUpOnceBase() { AttributeCache.init(getInstrumentation().getTargetContext()); @@ -208,6 +210,7 @@ class WindowTestsBase extends SystemServiceTestsBase { mSupervisor = mAtm.mTaskSupervisor; mRootWindowContainer = mAtm.mRootWindowContainer; mWm = mSystemServicesTestRule.getWindowManagerService(); + mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled; SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); @@ -279,6 +282,7 @@ class WindowTestsBase extends SystemServiceTestsBase { if (mUseFakeSettingsProvider) { FakeSettingsProvider.clearSettingsProvider(); } + mWm.mPerDisplayFocusEnabled = mOriginalPerDisplayFocusEnabled; } /** 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..c1a539ffac2c 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -789,15 +789,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) { @@ -875,11 +875,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(); @@ -916,16 +916,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { 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; diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index a67524887086..3553a5ace311 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -99,6 +99,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; @@ -424,6 +425,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 = */ " "); 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. |