diff options
254 files changed, 5349 insertions, 2776 deletions
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp index e84aea1e7fac..b6ea54d3d53f 100644 --- a/apct-tests/perftests/packagemanager/Android.bp +++ b/apct-tests/perftests/packagemanager/Android.bp @@ -34,6 +34,55 @@ android_test { test_suites: ["device-tests"], data: [ + ":QueriesAll4", + ":QueriesAll31", + ":QueriesAll43", + ":QueriesAll15", + ":QueriesAll27", + ":QueriesAll39", + ":QueriesAll11", + ":QueriesAll23", + ":QueriesAll35", + ":QueriesAll47", + ":QueriesAll9", + ":QueriesAll19", + ":QueriesAll1", + ":QueriesAll5", + ":QueriesAll40", + ":QueriesAll20", + ":QueriesAll32", + ":QueriesAll48", + ":QueriesAll16", + ":QueriesAll28", + ":QueriesAll44", + ":QueriesAll12", + ":QueriesAll24", + ":QueriesAll36", + ":QueriesAll6", + ":QueriesAll2", + ":QueriesAll41", + ":QueriesAll21", + ":QueriesAll37", + ":QueriesAll49", + ":QueriesAll17", + ":QueriesAll29", + ":QueriesAll33", + ":QueriesAll45", + ":QueriesAll13", + ":QueriesAll25", + ":QueriesAll7", + ":QueriesAll3", + ":QueriesAll30", + ":QueriesAll42", + ":QueriesAll10", + ":QueriesAll26", + ":QueriesAll38", + ":QueriesAll18", + ":QueriesAll22", + ":QueriesAll34", + ":QueriesAll46", + ":QueriesAll14", + ":QueriesAll8", ":QueriesAll0", ":perfetto_artifacts", ], diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java index c956bf52ed55..64b242334a8a 100644 --- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java @@ -16,6 +16,7 @@ package com.android.server.job; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -59,6 +60,19 @@ public interface JobSchedulerInternal { void reportAppUsage(String packageName, int userId); /** + * @return {@code true} if the given notification is associated with any user-initiated jobs. + */ + boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, + int userId, @NonNull String packageName); + + /** + * @return {@code true} if the given notification channel is associated with any user-initiated + * jobs. + */ + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, + int userId, String packageName); + + /** * Report a snapshot of sync-related jobs back to the sync manager */ JobStorePersistStats getPersistStats(); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 4477e94b77f1..8bd3d127c21b 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1925,6 +1925,20 @@ class JobConcurrencyManager { return null; } + @GuardedBy("mLock") + boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId, + String packageName) { + return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs( + notificationId, userId, packageName); + } + + @GuardedBy("mLock") + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, + int userId, String packageName) { + return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + notificationChannel, userId, packageName); + } + @NonNull private JobServiceContext createNewJobServiceContext() { return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator, diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java index 5a1214296526..f6e00ec24b33 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java @@ -31,6 +31,7 @@ import android.util.Slog; import android.util.SparseSetArray; import com.android.server.LocalServices; +import com.android.server.job.controllers.JobStatus; import com.android.server.notification.NotificationManagerInternal; class JobNotificationCoordinator { @@ -52,16 +53,18 @@ class JobNotificationCoordinator { @NonNull public final UserPackage userPackage; public final int notificationId; + public final String notificationChannel; public final int appPid; public final int appUid; @JobService.JobEndNotificationPolicy public final int jobEndNotificationPolicy; NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid, - int notificationId, + int notificationId, String notificationChannel, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { this.userPackage = userPackage; this.notificationId = notificationId; + this.notificationChannel = notificationChannel; this.appPid = appPid; this.appUid = appUid; this.jobEndNotificationPolicy = jobEndNotificationPolicy; @@ -84,14 +87,14 @@ class JobNotificationCoordinator { removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED); } final int userId = UserHandle.getUserId(callingUid); - // TODO(260848384): ensure apps can't cancel the notification for user-initiated job - // eg., by calling NotificationManager.cancel/All or deleting the notification channel - mNotificationManagerInternal.enqueueNotification( - packageName, packageName, callingUid, callingPid, /* tag */ null, - notificationId, notification, userId); + final JobStatus jobStatus = hostingContext.getRunningJobLocked(); + if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) { + notification.flags |= Notification.FLAG_USER_INITIATED_JOB; + } final UserPackage userPackage = UserPackage.of(userId, packageName); final NotificationDetails details = new NotificationDetails( - userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy); + userPackage, callingPid, callingUid, notificationId, notification.getChannelId(), + jobEndNotificationPolicy); SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage); if (appNotifications == null) { appNotifications = new SparseSetArray<>(); @@ -99,6 +102,11 @@ class JobNotificationCoordinator { } appNotifications.add(notificationId, hostingContext); mNotificationDetails.put(hostingContext, details); + // Call into NotificationManager after internal data structures have been updated since + // NotificationManager calls into this class to check for any existing associations. + mNotificationManagerInternal.enqueueNotification( + packageName, packageName, callingUid, callingPid, /* tag */ null, + notificationId, notification, userId); } void removeNotificationAssociation(@NonNull JobServiceContext hostingContext, @@ -113,6 +121,9 @@ class JobNotificationCoordinator { Slog.wtf(TAG, "Association data structures not in sync"); return; } + final String packageName = details.userPackage.packageName; + final int userId = UserHandle.getUserId(details.appUid); + boolean stripUijFlag = true; ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId); if (associatedContexts == null || associatedContexts.isEmpty()) { // No more jobs using this notification. Apply the final job stop policy. @@ -120,12 +131,63 @@ class JobNotificationCoordinator { // so the user doesn't get confused about the app state. if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE || stopReason == JobParameters.STOP_REASON_USER) { - final String packageName = details.userPackage.packageName; mNotificationManagerInternal.cancelNotification( packageName, packageName, details.appUid, details.appPid, /* tag */ null, - details.notificationId, UserHandle.getUserId(details.appUid)); + details.notificationId, userId); + stripUijFlag = false; + } + } else { + // Strip the UIJ flag only if there are no other UIJs associated with the notification + stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs( + details.notificationId, userId, packageName); + } + if (stripUijFlag) { + // Strip the user-initiated job flag from the notification. + mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification( + packageName, details.notificationId, userId); + } + } + + boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, + int userId, String packageName) { + final UserPackage pkgDetails = UserPackage.of(userId, packageName); + final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails); + if (associations == null) { + return false; + } + final ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId); + if (associatedContexts == null) { + return false; + } + + // Check if any UIJs associated with this package are using the same notification + for (int i = associatedContexts.size() - 1; i >= 0; i--) { + final JobStatus jobStatus = associatedContexts.valueAt(i).getRunningJobLocked(); + if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) { + return true; + } + } + return false; + } + + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, + int userId, String packageName) { + for (int i = mNotificationDetails.size() - 1; i >= 0; i--) { + final JobServiceContext jsc = mNotificationDetails.keyAt(i); + final NotificationDetails details = mNotificationDetails.get(jsc); + // Check if the details for the given notification match and if the associated job + // was started as a user initiated job + if (details != null + && UserHandle.getUserId(details.appUid) == userId + && details.userPackage.packageName.equals(packageName) + && details.notificationChannel.equals(notificationChannel)) { + final JobStatus jobStatus = jsc.getRunningJobLocked(); + if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) { + return true; + } } } + return false; } private void validateNotification(@NonNull String packageName, int callingUid, 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 644d92cc5eaa..aef9dd058658 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3712,6 +3712,30 @@ public class JobSchedulerService extends com.android.server.SystemService } @Override + public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, + int userId, String packageName) { + if (packageName == null) { + return false; + } + synchronized (mLock) { + return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs( + notificationId, userId, packageName); + } + } + + @Override + public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + String notificationChannel, int userId, String packageName) { + if (packageName == null || notificationChannel == null) { + return false; + } + synchronized (mLock) { + return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + notificationChannel, userId, packageName); + } + } + + @Override public JobStorePersistStats getPersistStats() { synchronized (mLock) { return new JobStorePersistStats(mJobs.getPersistStats()); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 873234a04460..ffbfe82be475 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -338,10 +338,12 @@ package android.app { } public class Notification implements android.os.Parcelable { + method public boolean isUserInitiatedJob(); method public boolean shouldShowForegroundImmediately(); field public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; field public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; field public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; + field public static final int FLAG_USER_INITIATED_JOB = 32768; // 0x8000 } public final class NotificationChannel implements android.os.Parcelable { @@ -351,10 +353,10 @@ package android.app { method public void setDeleted(boolean); method public void setDeletedTimeMs(long); method public void setDemoted(boolean); - method public void setFgServiceShown(boolean); method public void setImportanceLockedByCriticalDeviceFunction(boolean); method public void setImportantConversation(boolean); method public void setOriginalImportance(int); + method public void setUserVisibleTaskShown(boolean); } public final class NotificationChannelGroup implements android.os.Parcelable { diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 3615435b7d75..08a1af47ee64 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -2878,9 +2878,7 @@ public abstract class AccessibilityService extends Service { public IAccessibilityServiceClientWrapper(Context context, Looper looper, Callbacks callback) { - mCallback = callback; - mContext = context; - mExecutor = new HandlerExecutor(new Handler(looper)); + this(context, new HandlerExecutor(new Handler(looper)), callback); } public void init(IAccessibilityServiceConnection connection, int connectionId, diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 6dcf33114b19..808f25eb7cd9 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -860,10 +860,10 @@ public class AccessibilityServiceInfo implements Parcelable { * <p> * <strong>Generated by the system.</strong> * </p> - * @return The id. + * @return The id (or {@code null} if the component is not set yet). */ public String getId() { - return mComponentName.flattenToShortString(); + return mComponentName == null ? null : mComponentName.flattenToShortString(); } /** diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 7b4aeeca2855..29e135f8b0e9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -41,6 +41,7 @@ import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.RemoteServiceException.BadForegroundServiceNotificationException; +import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException; import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.RemoteServiceException.CrashedByAdbException; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; @@ -2078,6 +2079,9 @@ public final class ActivityThread extends ClientTransactionHandler case BadForegroundServiceNotificationException.TYPE_ID: throw new BadForegroundServiceNotificationException(message); + case BadUserInitiatedJobNotificationException.TYPE_ID: + throw new BadUserInitiatedJobNotificationException(message); + case MissingRequestPasswordComplexityPermissionException.TYPE_ID: throw new MissingRequestPasswordComplexityPermissionException(message); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7aedd30d660e..f80373912dcb 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -722,6 +722,16 @@ public class Notification implements Parcelable */ public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000; + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if this notification represents a currently running user-initiated job. + * + * This flag is for internal use only; applications cannot set this flag directly. + * @hide + */ + @TestApi + public static final int FLAG_USER_INITIATED_JOB = 0x00008000; + private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, @@ -731,7 +741,8 @@ public class Notification implements Parcelable @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, - FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) + FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE, + FLAG_USER_INITIATED_JOB}) @Retention(RetentionPolicy.SOURCE) public @interface NotificationFlags{}; @@ -4067,8 +4078,9 @@ public class Notification implements Parcelable * notification if alerts for this notification's group should be handled by a different * notification. This is only applicable for notifications that belong to a * {@link #setGroup(String) group}. This must be called on all notifications you want to - * mute. For example, if you want only the summary of your group to make noise, all - * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. + * mute. For example, if you want only the summary of your group to make noise and/or peek + * on screen, all children in the group should have the group alert behavior + * {@link #GROUP_ALERT_SUMMARY}. * * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> */ @@ -5726,7 +5738,8 @@ public class Notification implements Parcelable } private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { - boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null + boolean hideSnoozeButton = mN.isFgsOrUij() + || mN.fullScreenIntent != null || isBackgroundColorized(p) || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); @@ -6867,6 +6880,24 @@ public class Notification implements Parcelable } /** + * @return whether this notification is associated with a user initiated job + * @hide + */ + @TestApi + public boolean isUserInitiatedJob() { + return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0; + } + + /** + * @return whether this notification is associated with either a foreground service or + * a user initiated job + * @hide + */ + public boolean isFgsOrUij() { + return isForegroundService() || isUserInitiatedJob(); + } + + /** * Describe whether this notification's content such that it should always display * immediately when tied to a foreground service, even if the system might generally * avoid showing the notifications for short-lived foreground service lifetimes. diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 9615b684ee4b..746dcb6f2e13 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -32,7 +32,6 @@ import android.os.Parcelable; import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.text.TextUtils; -import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.Preconditions; @@ -150,6 +149,10 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_CONTENT_TYPE = "content_type"; private static final String ATT_SHOW_BADGE = "show_badge"; private static final String ATT_USER_LOCKED = "locked"; + /** + * This attribute represents both foreground services and user initiated jobs in U+. + * It was not renamed in U on purpose, in order to avoid creating an unnecessary migration path. + */ private static final String ATT_FG_SERVICE_SHOWN = "fgservice"; private static final String ATT_GROUP = "group"; private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system"; @@ -249,7 +252,7 @@ public final class NotificationChannel implements Parcelable { // Bitwise representation of fields that have been changed by the user, preventing the app from // making changes to these fields. private int mUserLockedFields; - private boolean mFgServiceShown; + private boolean mUserVisibleTaskShown; private boolean mVibrationEnabled; private boolean mShowBadge = DEFAULT_SHOW_BADGE; private boolean mDeleted = DEFAULT_DELETED; @@ -317,7 +320,7 @@ public final class NotificationChannel implements Parcelable { mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH); } mUserLockedFields = in.readInt(); - mFgServiceShown = in.readByte() != 0; + mUserVisibleTaskShown = in.readByte() != 0; mVibrationEnabled = in.readByte() != 0; mShowBadge = in.readByte() != 0; mDeleted = in.readByte() != 0; @@ -371,7 +374,7 @@ public final class NotificationChannel implements Parcelable { dest.writeByte(mLights ? (byte) 1 : (byte) 0); dest.writeLongArray(mVibration); dest.writeInt(mUserLockedFields); - dest.writeByte(mFgServiceShown ? (byte) 1 : (byte) 0); + dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0); dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0); dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0); dest.writeByte(mDeleted ? (byte) 1 : (byte) 0); @@ -418,8 +421,8 @@ public final class NotificationChannel implements Parcelable { * @hide */ @TestApi - public void setFgServiceShown(boolean shown) { - mFgServiceShown = shown; + public void setUserVisibleTaskShown(boolean shown) { + mUserVisibleTaskShown = shown; } /** @@ -845,8 +848,8 @@ public final class NotificationChannel implements Parcelable { /** * @hide */ - public boolean isFgServiceShown() { - return mFgServiceShown; + public boolean isUserVisibleTaskShown() { + return mUserVisibleTaskShown; } /** @@ -965,7 +968,7 @@ public final class NotificationChannel implements Parcelable { parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS)); setGroup(parser.getAttributeValue(null, ATT_GROUP)); lockFields(safeInt(parser, ATT_USER_LOCKED, 0)); - setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false)); + setUserVisibleTaskShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false)); setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); @@ -1072,8 +1075,8 @@ public final class NotificationChannel implements Parcelable { if (getUserLockedFields() != 0) { out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields()); } - if (isFgServiceShown()) { - out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isFgServiceShown()); + if (isUserVisibleTaskShown()) { + out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isUserVisibleTaskShown()); } if (canShowBadge()) { out.attributeBoolean(null, ATT_SHOW_BADGE, canShowBadge()); @@ -1147,7 +1150,7 @@ public final class NotificationChannel implements Parcelable { record.put(ATT_LIGHT_COLOR, Integer.toString(getLightColor())); record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate())); record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields())); - record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isFgServiceShown())); + record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown())); record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern())); record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge())); record.put(ATT_DELETED, Boolean.toString(isDeleted())); @@ -1239,7 +1242,7 @@ public final class NotificationChannel implements Parcelable { && mLights == that.mLights && getLightColor() == that.getLightColor() && getUserLockedFields() == that.getUserLockedFields() - && isFgServiceShown() == that.isFgServiceShown() + && isUserVisibleTaskShown() == that.isUserVisibleTaskShown() && mVibrationEnabled == that.mVibrationEnabled && mShowBadge == that.mShowBadge && isDeleted() == that.isDeleted() @@ -1265,8 +1268,8 @@ public final class NotificationChannel implements Parcelable { public int hashCode() { int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd, getLockscreenVisibility(), getSound(), mLights, getLightColor(), - getUserLockedFields(), - isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(), + getUserLockedFields(), isUserVisibleTaskShown(), + mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(), getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles, mImportanceLockedDefaultApp, mOriginalImportance, mParentId, mConversationId, mDemoted, mImportantConvo); @@ -1304,7 +1307,7 @@ public final class NotificationChannel implements Parcelable { + ", mLightColor=" + mLightColor + ", mVibration=" + Arrays.toString(mVibration) + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields) - + ", mFgServiceShown=" + mFgServiceShown + + ", mUserVisibleTaskShown=" + mUserVisibleTaskShown + ", mVibrationEnabled=" + mVibrationEnabled + ", mShowBadge=" + mShowBadge + ", mDeleted=" + mDeleted @@ -1342,7 +1345,7 @@ public final class NotificationChannel implements Parcelable { } } proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields); - proto.write(NotificationChannelProto.FG_SERVICE_SHOWN, mFgServiceShown); + proto.write(NotificationChannelProto.USER_VISIBLE_TASK_SHOWN, mUserVisibleTaskShown); proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled); proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge); proto.write(NotificationChannelProto.IS_DELETED, mDeleted); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index d2f2c3c4e95a..80f64e03afe8 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -726,8 +726,9 @@ public class NotificationManager { * Cancels a previously posted notification. * * <p>If the notification does not currently represent a - * {@link Service#startForeground(int, Notification) foreground service}, it will be - * removed from the UI and live + * {@link Service#startForeground(int, Notification) foreground service} or a + * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}, + * it will be removed from the UI and live * {@link android.service.notification.NotificationListenerService notification listeners} * will be informed so they can remove the notification from their UIs.</p> */ @@ -740,8 +741,9 @@ public class NotificationManager { * Cancels a previously posted notification. * * <p>If the notification does not currently represent a - * {@link Service#startForeground(int, Notification) foreground service}, it will be - * removed from the UI and live + * {@link Service#startForeground(int, Notification) foreground service} or a + * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}, + * it will be removed from the UI and live * {@link android.service.notification.NotificationListenerService notification listeners} * will be informed so they can remove the notification from their UIs.</p> */ @@ -754,8 +756,9 @@ public class NotificationManager { * Cancels a previously posted notification. * * <p>If the notification does not currently represent a - * {@link Service#startForeground(int, Notification) foreground service}, it will be - * removed from the UI and live + * {@link Service#startForeground(int, Notification) foreground service} or a + * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}, + * it will be removed from the UI and live * {@link android.service.notification.NotificationListenerService notification listeners} * will be informed so they can remove the notification from their UIs.</p> * diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index 620adbedc903..c5ad1105397e 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -102,6 +102,21 @@ public class RemoteServiceException extends AndroidRuntimeException { } /** + * Exception used to crash an app process when the system finds an error in a user-initiated job + * notification. + * + * @hide + */ + public static class BadUserInitiatedJobNotificationException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 6; + + public BadUserInitiatedJobNotificationException(String msg) { + super(msg); + } + } + + /** * Exception used to crash an app process when it calls a setting activity that requires * the {@code REQUEST_PASSWORD_COMPLEXITY} permission. * diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 86f6a93ede1e..658e08444006 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -46,6 +46,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.ArraySet; +import android.util.DebugUtils; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -111,6 +112,7 @@ public final class UiAutomation { private static final String LOG_TAG = UiAutomation.class.getSimpleName(); private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; private static final int CONNECTION_ID_UNDEFINED = -1; @@ -321,11 +323,18 @@ public final class UiAutomation { * @hide */ public void connectWithTimeout(int flags, long timeoutMillis) throws TimeoutException { + if (DEBUG) { + Log.d(LOG_TAG, "connectWithTimeout: user=" + Process.myUserHandle().getIdentifier() + + ", flags=" + DebugUtils.flagsToString(UiAutomation.class, "FLAG_", flags) + + ", timeout=" + timeoutMillis + "ms"); + } synchronized (mLock) { throwIfConnectedLocked(); if (mConnectionState == ConnectionState.CONNECTING) { + if (DEBUG) Log.d(LOG_TAG, "already connecting"); return; } + if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTING"); mConnectionState = ConnectionState.CONNECTING; mRemoteCallbackThread = new HandlerThread("UiAutomation"); mRemoteCallbackThread.start(); @@ -341,6 +350,7 @@ public final class UiAutomation { // If UiAutomation is not allowed to use the accessibility subsystem, the // connection state should keep disconnected and not to start the client connection. if (!useAccessibility()) { + if (DEBUG) Log.d(LOG_TAG, "setting state to DISCONNECTED"); mConnectionState = ConnectionState.DISCONNECTED; return; } @@ -357,6 +367,7 @@ public final class UiAutomation { final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; if (remainingTimeMillis <= 0) { + if (DEBUG) Log.d(LOG_TAG, "setting state to FAILED"); mConnectionState = ConnectionState.FAILED; throw new TimeoutException("Timeout while connecting " + this); } @@ -1367,7 +1378,8 @@ public final class UiAutomation { UserHandle userHandle) { try { if (DEBUG) { - Log.i(LOG_TAG, "Granting runtime permission"); + Log.i(LOG_TAG, "Granting runtime permission (" + permission + ") to package " + + packageName + " on user " + userHandle); } // Calling out without a lock held. mUiAutomationConnection.grantRuntimePermission(packageName, @@ -1592,7 +1604,7 @@ public final class UiAutomation { private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { public IAccessibilityServiceClientImpl(Looper looper, int generationId) { - super(null, looper, new Callbacks() { + super(/* context= */ null, looper, new Callbacks() { private final int mGenerationId = generationId; /** @@ -1606,10 +1618,21 @@ public final class UiAutomation { @Override public void init(int connectionId, IBinder windowToken) { + if (DEBUG) { + Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken=" + + windowToken + ", user=" + Process.myUserHandle() + + ", mGenerationId=" + mGenerationId + + ", UiAutomation.mGenerationId=" + + UiAutomation.this.mGenerationId); + } synchronized (mLock) { if (isGenerationChangedLocked()) { + if (DEBUG) { + Log.d(LOG_TAG, "init(): returning because generation id changed"); + } return; } + if (DEBUG) Log.d(LOG_TAG, "setting state to CONNECTED"); mConnectionState = ConnectionState.CONNECTED; mConnectionId = connectionId; mLock.notifyAll(); @@ -1662,9 +1685,20 @@ public final class UiAutomation { @Override public void onAccessibilityEvent(AccessibilityEvent event) { + if (VERBOSE) { + Log.v(LOG_TAG, "onAccessibilityEvent(" + Process.myUserHandle() + "): " + + event); + } + final OnAccessibilityEventListener listener; synchronized (mLock) { if (isGenerationChangedLocked()) { + if (VERBOSE) { + Log.v(LOG_TAG, "onAccessibilityEvent(): returning because " + + "generation id changed (from " + + UiAutomation.this.mGenerationId + " to " + + mGenerationId + ")"); + } return; } // It is not guaranteed that the accessibility framework sends events by the diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 3a32f2362c0d..13e800e38cca 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -103,6 +103,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public UiAutomationConnection() { + Log.d(TAG, "Created on user " + Process.myUserHandle()); } @Override diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index bad6c77a17f3..4d3338beded5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6413,7 +6413,7 @@ public class DevicePolicyManager { public void lockNow(@LockNowFlag int flags) { if (mService != null) { try { - mService.lockNow(flags, mParentInstance); + mService.lockNow(flags, mContext.getPackageName(), mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9634,7 +9634,8 @@ public class DevicePolicyManager { * @see #isProfileOwnerApp * @see #isDeviceOwnerApp * @param admin Which {@link DeviceAdminReceiver} this request is associate with. - * @param profileName The name of the profile. + * @param profileName The name of the profile. If the name is longer than 200 characters + * it will be truncated. * @throws SecurityException if {@code admin} is not a device or profile owner. */ public void setProfileName(@NonNull ComponentName admin, String profileName) { @@ -13646,8 +13647,8 @@ public class DevicePolicyManager { * privacy-sensitive events happening outside the managed profile would have been redacted * already. * - * @param admin Which device admin this request is associated with. Null if the caller is not - * a device admin + * @param admin Which device admin this request is associated with, or {@code null} + * if called by a delegated app. * @param enabled whether security logging should be enabled or not. * @throws SecurityException if the caller is not permitted to control security logging. * @see #setAffiliationIds @@ -13699,8 +13700,8 @@ public class DevicePolicyManager { * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown. * See {@link #isAffiliatedUser}. * - * @param admin Which device admin this request is associated with. Null if the caller is not - * a device admin. + * @param admin Which device admin this request is associated with, or {@code null} + * if called by a delegated app. * @return the new batch of security logs which is a list of {@link SecurityEvent}, * or {@code null} if rate limitation is exceeded or if logging is currently disabled. * @throws SecurityException if the caller is not allowed to access security logging, @@ -13857,8 +13858,8 @@ public class DevicePolicyManager { * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown. * See {@link #isAffiliatedUser}. * - * @param admin Which device admin this request is associated with. Null if the caller is not - * a device admin. + * @param admin Which device admin this request is associated with, or {@code null} + * if called by a delegated app. * @return Device logs from before the latest reboot of the system, or {@code null} if this API * is not supported on the device. * @throws SecurityException if the caller is not allowed to access security logging, or diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index a49891356ccd..593f73635617 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -1857,6 +1857,17 @@ public final class DevicePolicyResources { public static final String WORK_PROFILE_TELEPHONY_PAUSED_TURN_ON_BUTTON = PREFIX + "TURN_ON_WORK_PROFILE_BUTTON_TEXT"; + public static final String MINIRESOLVER_OPEN_IN_WORK = + PREFIX + "MINIRESOLVER_OPEN_IN_WORK"; + + public static final String MINIRESOLVER_OPEN_IN_PERSONAL = + PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL"; + + public static final String MINIRESOLVER_USE_WORK_BROWSER = + PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL"; + + public static final String MINIRESOLVER_USE_PERSONAL_BROWSER = + PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL"; } /** diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 8d508c0fb79d..9b0b18ac74ec 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -119,7 +119,7 @@ interface IDevicePolicyManager { void setRequiredStrongAuthTimeout(in ComponentName who, String callerPackageName, long timeMs, boolean parent); long getRequiredStrongAuthTimeout(in ComponentName who, int userId, boolean parent); - void lockNow(int flags, boolean parent); + void lockNow(int flags, String callerPackageName, boolean parent); /** * @param factoryReset only applicable when `targetSdk >= U`, either tries to factoryReset/fail or removeUser/fail otherwise diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index da6784be4404..2ca2b79bcc08 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -64,6 +64,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.IntConsumer; @@ -171,6 +172,7 @@ public final class VirtualDeviceManager { public VirtualDevice createVirtualDevice( int associationId, @NonNull VirtualDeviceParams params) { + Objects.requireNonNull(params, "params must not be null"); try { return new VirtualDevice(mService, mContext, associationId, params); } catch (RemoteException e) { @@ -409,6 +411,9 @@ public final class VirtualDeviceManager { @NonNull PendingIntent pendingIntent, @NonNull Executor executor, @NonNull IntConsumer listener) { + Objects.requireNonNull(pendingIntent, "pendingIntent must not be null"); + Objects.requireNonNull(executor, "executor must not be null"); + Objects.requireNonNull(listener, "listener must not be null"); mVirtualDeviceInternal.launchPendingIntent( displayId, pendingIntent, executor, listener); } @@ -483,6 +488,7 @@ public final class VirtualDeviceManager { @NonNull VirtualDisplayConfig config, @Nullable @CallbackExecutor Executor executor, @Nullable VirtualDisplay.Callback callback) { + Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualDisplay(config, executor, callback); } @@ -503,6 +509,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) { + Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualDpad(config); } @@ -514,6 +521,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) { + Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualKeyboard(config); } @@ -550,6 +558,7 @@ public final class VirtualDeviceManager { @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @NonNull public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) { + Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualMouse(config); } @@ -587,6 +596,7 @@ public final class VirtualDeviceManager { @NonNull public VirtualTouchscreen createVirtualTouchscreen( @NonNull VirtualTouchscreenConfig config) { + Objects.requireNonNull(config, "config must not be null"); return mVirtualDeviceInternal.createVirtualTouchscreen(config); } @@ -659,6 +669,7 @@ public final class VirtualDeviceManager { @NonNull VirtualDisplay display, @Nullable Executor executor, @Nullable AudioConfigurationChangeCallback callback) { + Objects.requireNonNull(display, "display must not be null"); return mVirtualDeviceInternal.createVirtualAudioDevice(display, executor, callback); } diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl index 96a42e24bc1a..563ed7dd6e7a 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -38,6 +38,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; import android.os.ParcelFileDescriptor; +import android.window.IDumpCallback; import com.android.internal.infra.AndroidFuture; @@ -116,4 +117,10 @@ interface ILauncherApps { String getShortcutIconUri(String callingPackage, String packageName, String shortcutId, int userId); Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage, int userId); + + /** Register a callback to be called right before the wmtrace data is moved to the bugreport. */ + void registerDumpCallback(IDumpCallback cb); + + /** Unregister a callback, so that it won't be called when LauncherApps dumps. */ + void unRegisterDumpCallback(IDumpCallback cb); } diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 8989006a7e83..27270d9f378f 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -17,6 +17,7 @@ package android.content.pm; import static android.Manifest.permission; +import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; import android.annotation.IntDef; @@ -68,6 +69,7 @@ import android.util.ArrayMap; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; +import android.window.IDumpCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; @@ -1172,6 +1174,32 @@ public class LauncherApps { } /** + * Register a callback to be called right before the wmtrace data is moved to the bugreport. + * @hide + */ + @RequiresPermission(READ_FRAME_BUFFER) + public void registerDumpCallback(IDumpCallback cb) { + try { + mService.registerDumpCallback(cb); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** + * Unregister a callback, so that it won't be called when LauncherApps dumps. + * @hide + */ + @RequiresPermission(READ_FRAME_BUFFER) + public void unRegisterDumpCallback(IDumpCallback cb) { + try { + mService.unRegisterDumpCallback(cb); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + + /** * Returns {@link ShortcutInfo}s that match {@code query}. * * <p>Callers must be allowed to access the shortcut information, as defined in {@link diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index cb988dfdb203..30fd77ca467c 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2991,6 +2991,10 @@ public class PackageInstaller { * The update ownership enforcement can only be enabled on initial installation. Set * this to {@code true} on package update is a no-op. * + * Apps may opt themselves out of update ownership by setting the + * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a> + * attribute in their manifest to <code>false</code>. + * * Note: To enable the update ownership enforcement, the installer must have the * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP} * permission. diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java index 269bec256282..408f7ed9f766 100644 --- a/core/java/android/content/pm/parsing/ApkLite.java +++ b/core/java/android/content/pm/parsing/ApkLite.java @@ -138,6 +138,11 @@ public class ApkLite { */ private final boolean mIsSdkLibrary; + /** + * Indicates if this package allows an installer to declare update ownership of it. + */ + private final boolean mAllowUpdateOwnership; + public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit, String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode, int versionCodeMajor, int revisionCode, int installLocation, @@ -148,7 +153,7 @@ public class ApkLite { String requiredSystemPropertyName, String requiredSystemPropertyValue, int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy, Set<String> requiredSplitTypes, Set<String> splitTypes, - boolean hasDeviceAdminReceiver, boolean isSdkLibrary) { + boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean allowUpdateOwnership) { mPath = path; mPackageName = packageName; mSplitName = splitName; @@ -182,6 +187,7 @@ public class ApkLite { mRollbackDataPolicy = rollbackDataPolicy; mHasDeviceAdminReceiver = hasDeviceAdminReceiver; mIsSdkLibrary = isSdkLibrary; + mAllowUpdateOwnership = allowUpdateOwnership; } /** @@ -474,6 +480,9 @@ public class ApkLite { return mRollbackDataPolicy; } + /** + * Indicates if this app contains a {@link android.app.admin.DeviceAdminReceiver}. + */ @DataClass.Generated.Member public boolean isHasDeviceAdminReceiver() { return mHasDeviceAdminReceiver; @@ -487,11 +496,19 @@ public class ApkLite { return mIsSdkLibrary; } + /** + * Indicates if this package allows an installer to declare update ownership of it. + */ + @DataClass.Generated.Member + public boolean isAllowUpdateOwnership() { + return mAllowUpdateOwnership; + } + @DataClass.Generated( - time = 1643063342990L, + time = 1680122754650L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java", - inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 64fed63c7159..a4339d41dfd2 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -127,7 +127,8 @@ public class ApkLiteParseUtils { null /* isFeatureSplits */, null /* usesSplitNames */, null /* configForSplit */, null /* splitApkPaths */, null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(), - null /* requiredSplitTypes */, null /* splitTypes */)); + null /* requiredSplitTypes */, null, /* splitTypes */ + baseApk.isAllowUpdateOwnership())); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -273,7 +274,8 @@ public class ApkLiteParseUtils { return input.success( new PackageLite(codePath, baseCodePath, baseApk, splitNames, isFeatureSplits, usesSplitNames, configForSplits, splitCodePaths, splitRevisionCodes, - baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes)); + baseApk.getTargetSdkVersion(), requiredSplitTypes, splitTypes, + baseApk.isAllowUpdateOwnership())); } /** @@ -400,6 +402,8 @@ public class ApkLiteParseUtils { "isFeatureSplit", false); boolean isSplitRequired = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, "isSplitRequired", false); + boolean allowUpdateOwnership = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE, + "allowUpdateOwnership", true); String configForSplit = parser.getAttributeValue(null, "configForSplit"); int targetSdkVersion = DEFAULT_TARGET_SDK_VERSION; @@ -583,7 +587,7 @@ public class ApkLiteParseUtils { overlayIsStatic, overlayPriority, requiredSystemPropertyName, requiredSystemPropertyValue, minSdkVersion, targetSdkVersion, rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second, - hasDeviceAdminReceiver, isSdkLibrary)); + hasDeviceAdminReceiver, isSdkLibrary, allowUpdateOwnership)); } private static boolean isDeviceAdminReceiver( diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java index e2789c93516f..e24b9320110e 100644 --- a/core/java/android/content/pm/parsing/PackageLite.java +++ b/core/java/android/content/pm/parsing/PackageLite.java @@ -110,10 +110,16 @@ public class PackageLite { */ private final boolean mIsSdkLibrary; + /** + * Indicates if this package allows an installer to declare update ownership of it. + */ + private final boolean mAllowUpdateOwnership; + public PackageLite(String path, String baseApkPath, ApkLite baseApk, String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames, String[] configForSplit, String[] splitApkPaths, int[] splitRevisionCodes, - int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes) { + int targetSdk, Set<String>[] requiredSplitTypes, Set<String>[] splitTypes, + boolean allowUpdateOwnership) { // The following paths may be different from the path in ApkLite because we // move or rename the APK files. Use parameters to indicate the correct paths. mPath = path; @@ -144,6 +150,7 @@ public class PackageLite { mSplitApkPaths = splitApkPaths; mSplitRevisionCodes = splitRevisionCodes; mTargetSdk = targetSdk; + mAllowUpdateOwnership = allowUpdateOwnership; } /** @@ -414,12 +421,19 @@ public class PackageLite { return mIsSdkLibrary; } + /** + * Indicates if this package allows an installer to declare update ownership of it. + */ + @DataClass.Generated.Member + public boolean isAllowUpdateOwnership() { + return mAllowUpdateOwnership; + } + @DataClass.Generated( - time = 1643132127068L, + time = 1680125514341L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java", - inputSignatures = - "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") + inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mAllowUpdateOwnership\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)") @Deprecated private void __metadata() {} diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java index 00ce17adfda6..9140d02e223d 100644 --- a/core/java/android/credentials/CredentialManager.java +++ b/core/java/android/credentials/CredentialManager.java @@ -460,9 +460,17 @@ public final class CredentialManager { return false; } + /** + * Returns whether the service is enabled. + * + * @hide + */ private boolean isServiceEnabled() { - return DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true); + try { + return mService.isServiceEnabled(); + } catch (RemoteException e) { + return false; + } } /** diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl index 5fde96b0b9ff..b779c56035d3 100644 --- a/core/java/android/credentials/ICredentialManager.aidl +++ b/core/java/android/credentials/ICredentialManager.aidl @@ -58,5 +58,7 @@ interface ICredentialManager { List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter); List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter); + + boolean isServiceEnabled(); } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index b5281a5025b8..2aead3c22deb 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1758,10 +1758,12 @@ public final class DisplayManager { /** * Key for the brightness throttling data as a String formatted: * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>] - * Where the latter part is repeated for each throttling level, and the entirety is repeated - * for each display, separated by a semicolon. + * [,<throttlingId>]? + * Where [<severity as string>,<brightness cap>] is repeated for each throttling level. + * The entirety is repeated for each display and throttling id, separated by a semicolon. * For example: * 123,1,critical,0.8;456,2,moderate,0.9,critical,0.7 + * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7 */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; } diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java index b97760656059..e87333fe4941 100644 --- a/core/java/android/service/credentials/CredentialProviderService.java +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -231,12 +231,18 @@ public abstract class CredentialProviderService extends Service { } private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() { - public ICancellationSignal onBeginGetCredential(BeginGetCredentialRequest request, + @Override + public void onBeginGetCredential(BeginGetCredentialRequest request, IBeginGetCredentialCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); + try { + callback.onCancellable(transport); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } mHandler.sendMessage(obtainMessage( CredentialProviderService::onBeginGetCredential, @@ -267,7 +273,6 @@ public abstract class CredentialProviderService extends Service { } } )); - return transport; } private void enforceRemoteEntryPermission() { String permission = @@ -280,12 +285,17 @@ public abstract class CredentialProviderService extends Service { } @Override - public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request, + public void onBeginCreateCredential(BeginCreateCredentialRequest request, IBeginCreateCredentialCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); + try { + callback.onCancellable(transport); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } mHandler.sendMessage(obtainMessage( CredentialProviderService::onBeginCreateCredential, @@ -316,16 +326,20 @@ public abstract class CredentialProviderService extends Service { } } )); - return transport; } @Override - public ICancellationSignal onClearCredentialState(ClearCredentialStateRequest request, + public void onClearCredentialState(ClearCredentialStateRequest request, IClearCredentialStateCallback callback) { Objects.requireNonNull(request); Objects.requireNonNull(callback); ICancellationSignal transport = CancellationSignal.createTransport(); + try { + callback.onCancellable(transport); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } mHandler.sendMessage(obtainMessage( CredentialProviderService::onClearCredentialState, @@ -350,7 +364,6 @@ public abstract class CredentialProviderService extends Service { } } )); - return transport; } }; diff --git a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl index ab855ef0b13f..4b73cbc77ee3 100644 --- a/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl +++ b/core/java/android/service/credentials/IBeginCreateCredentialCallback.aidl @@ -1,6 +1,7 @@ package android.service.credentials; import android.service.credentials.BeginCreateCredentialResponse; +import android.os.ICancellationSignal; /** * Interface from the system to a credential provider service. @@ -10,4 +11,5 @@ import android.service.credentials.BeginCreateCredentialResponse; oneway interface IBeginCreateCredentialCallback { void onSuccess(in BeginCreateCredentialResponse request); void onFailure(String errorType, in CharSequence message); + void onCancellable(in ICancellationSignal cancellation); }
\ No newline at end of file diff --git a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl index 73e98707d15e..0710fb3a97ea 100644 --- a/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl +++ b/core/java/android/service/credentials/IBeginGetCredentialCallback.aidl @@ -1,6 +1,8 @@ package android.service.credentials; import android.service.credentials.BeginGetCredentialResponse; +import android.os.ICancellationSignal; + /** * Interface from the system to a credential provider service. @@ -10,4 +12,5 @@ import android.service.credentials.BeginGetCredentialResponse; oneway interface IBeginGetCredentialCallback { void onSuccess(in BeginGetCredentialResponse response); void onFailure(String errorType, in CharSequence message); + void onCancellable(in ICancellationSignal cancellation); }
\ No newline at end of file diff --git a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl index ec805d0a1799..57513186fe3d 100644 --- a/core/java/android/service/credentials/IClearCredentialStateCallback.aidl +++ b/core/java/android/service/credentials/IClearCredentialStateCallback.aidl @@ -16,12 +16,16 @@ package android.service.credentials; +import android.os.ICancellationSignal; + + /** * Callback for onClearCredentialState request. * * @hide */ -interface IClearCredentialStateCallback { - oneway void onSuccess(); - oneway void onFailure(String errorType, CharSequence message); +oneway interface IClearCredentialStateCallback { + void onSuccess(); + void onFailure(String errorType, CharSequence message); + void onCancellable(in ICancellationSignal cancellation); }
\ No newline at end of file diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl index 626dd7858626..ebb4a4ef6cca 100644 --- a/core/java/android/service/credentials/ICredentialProviderService.aidl +++ b/core/java/android/service/credentials/ICredentialProviderService.aidl @@ -30,8 +30,8 @@ import android.os.ICancellationSignal; * * @hide */ -interface ICredentialProviderService { - ICancellationSignal onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback); - ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback); - ICancellationSignal onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback); +oneway interface ICredentialProviderService { + void onBeginGetCredential(in BeginGetCredentialRequest request, in IBeginGetCredentialCallback callback); + void onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback); + void onClearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback); } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 5c2b38963005..402da28b3c5c 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -1008,9 +1008,8 @@ public class ZenModeConfig implements Parcelable { .allowAlarms(allowAlarms) .allowMedia(allowMedia) .allowSystem(allowSystem) - .allowConversations(allowConversations - ? ZenModeConfig.getZenPolicySenders(allowConversationsFrom) - : ZenPolicy.PEOPLE_TYPE_NONE); + .allowConversations(allowConversations ? allowConversationsFrom + : ZenPolicy.CONVERSATION_SENDERS_NONE); if (suppressedVisualEffects == 0) { builder.showAllVisualEffects(); } else { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c30b5953adce..2f5cd5434b89 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -265,6 +265,7 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV; private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV; private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV; + private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV; private static final boolean DEBUG_BLAST = false || LOCAL_LOGV; private static final int LOGTAG_INPUT_FOCUS = 62001; @@ -7122,7 +7123,8 @@ public final class ViewRootImpl implements ViewParent, mJoystick.cancel(); } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { - mTouchNavigation.cancel(event); + // Touch navigation events cannot be cancelled since they are dispatched + // immediately. } } } @@ -7641,392 +7643,109 @@ public final class ViewRootImpl implements ViewParent, } /** - * Creates dpad events from unhandled touch navigation movements. + * Creates DPAD events from unhandled touch navigation movements. */ final class SyntheticTouchNavigationHandler extends Handler { private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler"; - private static final boolean LOCAL_DEBUG = false; - - // Assumed nominal width and height in millimeters of a touch navigation pad, - // if no resolution information is available from the input system. - private static final float DEFAULT_WIDTH_MILLIMETERS = 48; - private static final float DEFAULT_HEIGHT_MILLIMETERS = 48; - - /* TODO: These constants should eventually be moved to ViewConfiguration. */ - - // The nominal distance traveled to move by one unit. - private static final int TICK_DISTANCE_MILLIMETERS = 12; - - // Minimum and maximum fling velocity in ticks per second. - // The minimum velocity should be set such that we perform enough ticks per - // second that the fling appears to be fluid. For example, if we set the minimum - // to 2 ticks per second, then there may be up to half a second delay between the next - // to last and last ticks which is noticeably discrete and jerky. This value should - // probably not be set to anything less than about 4. - // If fling accuracy is a problem then consider tuning the tick distance instead. - private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f; - private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f; - - // Fling velocity decay factor applied after each new key is emitted. - // This parameter controls the deceleration and overall duration of the fling. - // The fling stops automatically when its velocity drops below the minimum - // fling velocity defined above. - private static final float FLING_TICK_DECAY = 0.8f; - - /* The input device that we are tracking. */ + // The id of the input device that is being tracked. private int mCurrentDeviceId = -1; private int mCurrentSource; - private boolean mCurrentDeviceSupported; - - /* Configuration for the current input device. */ - // The scaled tick distance. A movement of this amount should generally translate - // into a single dpad event in a given direction. - private float mConfigTickDistance; - - // The minimum and maximum scaled fling velocity. - private float mConfigMinFlingVelocity; - private float mConfigMaxFlingVelocity; - - /* Tracking state. */ - - // The velocity tracker for detecting flings. - private VelocityTracker mVelocityTracker; + private int mPendingKeyMetaState; - // The active pointer id, or -1 if none. - private int mActivePointerId = -1; + private final GestureDetector mGestureDetector = new GestureDetector(mContext, + new GestureDetector.OnGestureListener() { + @Override + public boolean onDown(@NonNull MotionEvent e) { + // This can be ignored since it's not clear what KeyEvent this will + // belong to. + return true; + } - // Location where tracking started. - private float mStartX; - private float mStartY; + @Override + public void onShowPress(@NonNull MotionEvent e) { - // Most recently observed position. - private float mLastX; - private float mLastY; + } - // Accumulated movement delta since the last direction key was sent. - private float mAccumulatedX; - private float mAccumulatedY; + @Override + public boolean onSingleTapUp(@NonNull MotionEvent e) { + dispatchTap(e.getEventTime()); + return true; + } - // Set to true if any movement was delivered to the app. - // Implies that tap slop was exceeded. - private boolean mConsumedMovement; + @Override + public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float distanceX, float distanceY) { + // Scroll doesn't translate to DPAD events so should be ignored. + return true; + } - // The most recently sent key down event. - // The keycode remains set until the direction changes or a fling ends - // so that repeated key events may be generated as required. - private long mPendingKeyDownTime; - private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN; - private int mPendingKeyRepeatCount; - private int mPendingKeyMetaState; + @Override + public void onLongPress(@NonNull MotionEvent e) { + // Long presses don't translate to DPAD events so should be ignored. + } - // The current fling velocity while a fling is in progress. - private boolean mFlinging; - private float mFlingVelocity; + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + dispatchFling(velocityX, velocityY, e2.getEventTime()); + return true; + } + }); - public SyntheticTouchNavigationHandler() { + SyntheticTouchNavigationHandler() { super(true); } public void process(MotionEvent event) { + if (event.getDevice() == null) { + // The current device is not supported. + if (DEBUG_TOUCH_NAVIGATION) { + Log.d(LOCAL_TAG, + "Current device not supported so motion event is not processed"); + } + return; + } + mPendingKeyMetaState = event.getMetaState(); // Update the current device information. - final long time = event.getEventTime(); final int deviceId = event.getDeviceId(); final int source = event.getSource(); if (mCurrentDeviceId != deviceId || mCurrentSource != source) { - finishKeys(time); - finishTracking(time); mCurrentDeviceId = deviceId; mCurrentSource = source; - mCurrentDeviceSupported = false; - InputDevice device = event.getDevice(); - if (device != null) { - // In order to support an input device, we must know certain - // characteristics about it, such as its size and resolution. - InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X); - InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y); - if (xRange != null && yRange != null) { - mCurrentDeviceSupported = true; - - // Infer the resolution if it not actually known. - float xRes = xRange.getResolution(); - if (xRes <= 0) { - xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS; - } - float yRes = yRange.getResolution(); - if (yRes <= 0) { - yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS; - } - float nominalRes = (xRes + yRes) * 0.5f; - - // Precompute all of the configuration thresholds we will need. - mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes; - mConfigMinFlingVelocity = - MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance; - mConfigMaxFlingVelocity = - MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance; - - if (LOCAL_DEBUG) { - Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId - + " (" + Integer.toHexString(mCurrentSource) + "): " - + ", mConfigTickDistance=" + mConfigTickDistance - + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity - + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity); - } - } - } - } - if (!mCurrentDeviceSupported) { - return; - } - - // Handle the event. - final int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: { - boolean caughtFling = mFlinging; - finishKeys(time); - finishTracking(time); - mActivePointerId = event.getPointerId(0); - mVelocityTracker = VelocityTracker.obtain(); - mVelocityTracker.addMovement(event); - mStartX = event.getX(); - mStartY = event.getY(); - mLastX = mStartX; - mLastY = mStartY; - mAccumulatedX = 0; - mAccumulatedY = 0; - - // If we caught a fling, then pretend that the tap slop has already - // been exceeded to suppress taps whose only purpose is to stop the fling. - mConsumedMovement = caughtFling; - break; - } - - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_UP: { - if (mActivePointerId < 0) { - break; - } - final int index = event.findPointerIndex(mActivePointerId); - if (index < 0) { - finishKeys(time); - finishTracking(time); - break; - } - - mVelocityTracker.addMovement(event); - final float x = event.getX(index); - final float y = event.getY(index); - mAccumulatedX += x - mLastX; - mAccumulatedY += y - mLastY; - mLastX = x; - mLastY = y; - - // Consume any accumulated movement so far. - final int metaState = event.getMetaState(); - consumeAccumulatedMovement(time, metaState); - - // Detect taps and flings. - if (action == MotionEvent.ACTION_UP) { - if (mConsumedMovement && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { - // It might be a fling. - mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity); - final float vx = mVelocityTracker.getXVelocity(mActivePointerId); - final float vy = mVelocityTracker.getYVelocity(mActivePointerId); - if (!startFling(time, vx, vy)) { - finishKeys(time); - } - } - finishTracking(time); - } - break; - } - - case MotionEvent.ACTION_CANCEL: { - finishKeys(time); - finishTracking(time); - break; - } - } - } - - public void cancel(MotionEvent event) { - if (mCurrentDeviceId == event.getDeviceId() - && mCurrentSource == event.getSource()) { - final long time = event.getEventTime(); - finishKeys(time); - finishTracking(time); } - } - - private void finishKeys(long time) { - cancelFling(); - sendKeyUp(time); - } - - private void finishTracking(long time) { - if (mActivePointerId >= 0) { - mActivePointerId = -1; - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - private void consumeAccumulatedMovement(long time, int metaState) { - final float absX = Math.abs(mAccumulatedX); - final float absY = Math.abs(mAccumulatedY); - if (absX >= absY) { - if (absX >= mConfigTickDistance) { - mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT); - mAccumulatedY = 0; - mConsumedMovement = true; - } - } else { - if (absY >= mConfigTickDistance) { - mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY, - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN); - mAccumulatedX = 0; - mConsumedMovement = true; - } - } + // Interpret the event. + mGestureDetector.onTouchEvent(event); } - private float consumeAccumulatedMovement(long time, int metaState, - float accumulator, int negativeKeyCode, int positiveKeyCode) { - while (accumulator <= -mConfigTickDistance) { - sendKeyDownOrRepeat(time, negativeKeyCode, metaState); - accumulator += mConfigTickDistance; - } - while (accumulator >= mConfigTickDistance) { - sendKeyDownOrRepeat(time, positiveKeyCode, metaState); - accumulator -= mConfigTickDistance; - } - return accumulator; + private void dispatchTap(long time) { + dispatchEvent(time, KeyEvent.KEYCODE_DPAD_CENTER); } - private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) { - if (mPendingKeyCode != keyCode) { - sendKeyUp(time); - mPendingKeyDownTime = time; - mPendingKeyCode = keyCode; - mPendingKeyRepeatCount = 0; + private void dispatchFling(float x, float y, long time) { + if (Math.abs(x) > Math.abs(y)) { + dispatchEvent(time, + x > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT); } else { - mPendingKeyRepeatCount += 1; - } - mPendingKeyMetaState = metaState; - - // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1 - // but it doesn't quite make sense when simulating the events in this way. - if (LOCAL_DEBUG) { - Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode - + ", repeatCount=" + mPendingKeyRepeatCount - + ", metaState=" + Integer.toHexString(mPendingKeyMetaState)); - } - enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time, - KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount, - mPendingKeyMetaState, mCurrentDeviceId, - KeyEvent.FLAG_FALLBACK, mCurrentSource)); - } - - private void sendKeyUp(long time) { - if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { - if (LOCAL_DEBUG) { - Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode - + ", metaState=" + Integer.toHexString(mPendingKeyMetaState)); - } - enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time, - KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState, - mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK, - mCurrentSource)); - mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN; - } - } - - private boolean startFling(long time, float vx, float vy) { - if (LOCAL_DEBUG) { - Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy - + ", min=" + mConfigMinFlingVelocity); - } - - // Flings must be oriented in the same direction as the preceding movements. - switch (mPendingKeyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (-vx >= mConfigMinFlingVelocity - && Math.abs(vy) < mConfigMinFlingVelocity) { - mFlingVelocity = -vx; - break; - } - return false; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (vx >= mConfigMinFlingVelocity - && Math.abs(vy) < mConfigMinFlingVelocity) { - mFlingVelocity = vx; - break; - } - return false; - - case KeyEvent.KEYCODE_DPAD_UP: - if (-vy >= mConfigMinFlingVelocity - && Math.abs(vx) < mConfigMinFlingVelocity) { - mFlingVelocity = -vy; - break; - } - return false; - - case KeyEvent.KEYCODE_DPAD_DOWN: - if (vy >= mConfigMinFlingVelocity - && Math.abs(vx) < mConfigMinFlingVelocity) { - mFlingVelocity = vy; - break; - } - return false; - } - - // Post the first fling event. - mFlinging = postFling(time); - return mFlinging; - } - - private boolean postFling(long time) { - // The idea here is to estimate the time when the pointer would have - // traveled one tick distance unit given the current fling velocity. - // This effect creates continuity of motion. - if (mFlingVelocity >= mConfigMinFlingVelocity) { - long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000); - postAtTime(mFlingRunnable, time + delay); - if (LOCAL_DEBUG) { - Log.d(LOCAL_TAG, "Posted fling: velocity=" - + mFlingVelocity + ", delay=" + delay - + ", keyCode=" + mPendingKeyCode); - } - return true; + dispatchEvent(time, y > 0 ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP); } - return false; } - private void cancelFling() { - if (mFlinging) { - removeCallbacks(mFlingRunnable); - mFlinging = false; + private void dispatchEvent(long time, int keyCode) { + if (DEBUG_TOUCH_NAVIGATION) { + Log.d(LOCAL_TAG, "Dispatching DPAD events DOWN and UP with keycode " + keyCode); } + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, keyCode, /* repeat= */ 0, mPendingKeyMetaState, + mCurrentDeviceId, /* scancode= */ 0, KeyEvent.FLAG_FALLBACK, + mCurrentSource)); + enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, keyCode, /* repeat= */ 0, mPendingKeyMetaState, + mCurrentDeviceId, /* scancode= */ 0, KeyEvent.FLAG_FALLBACK, + mCurrentSource)); } - - private final Runnable mFlingRunnable = new Runnable() { - @Override - public void run() { - final long time = SystemClock.uptimeMillis(); - sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState); - mFlingVelocity *= FLING_TICK_DECAY; - if (!postFling(time)) { - mFlinging = false; - finishKeys(time); - } - } - }; } final class SyntheticKeyboardHandler { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 48686fc25916..02b34786e9f2 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -869,6 +869,42 @@ public interface WindowManager extends ViewManager { "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; /** + * Application level {@link android.content.pm.PackageManager.Property PackageManager.Property} + * for an app to inform the system that the app can be opted-out from the compatibility + * treatment that avoids {@link android.app.Activity#setRequestedOrientation} loops. The loop + * can be trigerred by ignoreRequestedOrientation display setting enabled on the device or + * by the landscape natural orientation of the device. + * + * <p>The system could ignore {@link android.app.Activity#setRequestedOrientation} + * call from an app if both of the following conditions are true: + * <ul> + * <li>Activity has requested orientation more than 2 times within 1-second timer + * <li>Activity is not letterboxed for fixed orientation + * </ul> + * + * <p>Setting this property to {@code false} informs the system that the app must be + * opted-out from the compatibility treatment even if the device manufacturer has opted the app + * into the treatment. + * + * <p>Not setting this property at all, or setting this property to {@code true} has no effect. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name= + * "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED" + * android:value="false"/> + * </application> + * </pre> + * + * @hide + */ + // TODO(b/274924641): Make this public API. + String PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = + "android.window.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED"; + + /** * Application level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that it needs to be opted-out from the * compatibility treatment that sandboxes {@link android.view.View} API. diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index 38b564ade07d..d69c781a6ac9 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -976,6 +976,7 @@ public class AccessibilityRecord { append(builder, "AddedCount", mAddedCount); append(builder, "RemovedCount", mRemovedCount); append(builder, "ParcelableData", mParcelableData); + append(builder, "DisplayId", mSourceDisplayId); builder.append(" ]"); return builder; } diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java index 4aa612c526fe..951eeccf4d8a 100644 --- a/core/java/android/view/autofill/AutofillFeatureFlags.java +++ b/core/java/android/view/autofill/AutofillFeatureFlags.java @@ -194,6 +194,14 @@ public class AutofillFeatureFlags { "should_enable_autofill_on_all_view_types"; /** + * Whether to enable multi-line filter when checking if view is autofillable + * + * @hide + */ + public static final String DEVICE_CONFIG_MULTILINE_FILTER_ENABLED = + "multiline_filter_enabled"; + + /** * Whether include all autofill type not none views in assist structure * * @hide @@ -439,6 +447,17 @@ public class AutofillFeatureFlags { } + /** + * Whether should enable multi-line filter + * + * @hide + */ + public static boolean shouldEnableMultilineFilter() { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_AUTOFILL, + DEVICE_CONFIG_MULTILINE_FILTER_ENABLED, false); + } + // START AUTOFILL PCC CLASSIFICATION FUNCTIONS /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 801b13a2c69c..f7b7d3387938 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -707,6 +707,9 @@ public final class AutofillManager { // An allowed activity set read from device config private Set<String> mAllowedActivitySet = new ArraySet<>(); + // Whether to enable multi-line check when checking whether view is autofillable + private boolean mShouldEnableMultilineFilter; + // Indicate whether should include all view with autofill type not none in assist structure private boolean mShouldIncludeAllViewsWithAutofillTypeNotNoneInAssistStructure; @@ -889,6 +892,9 @@ public final class AutofillManager { mNonAutofillableImeActionIdSet = AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag(); + mShouldEnableMultilineFilter = + AutofillFeatureFlags.shouldEnableMultilineFilter(); + final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag(); final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag(); @@ -948,9 +954,8 @@ public final class AutofillManager { /** * Whether view passes the imeAction check * - * @hide */ - public boolean isPassingImeActionCheck(EditText editText) { + private boolean isPassingImeActionCheck(EditText editText) { final int actionId = editText.getImeOptions(); if (mNonAutofillableImeActionIdSet.contains(String.valueOf(actionId))) { Log.d(TAG, "view not autofillable - not passing ime action check"); @@ -959,6 +964,21 @@ public final class AutofillManager { return true; } + /** + * Checks whether the view passed in is not multiline text + * + * @param editText the view that passed to this check + * @return true if the view input is not multiline, false otherwise + */ + private boolean isPassingMultilineCheck(EditText editText) { + // check if min line is set to be greater than 1 + if (editText.getMinLines() > 1) { + Log.d(TAG, "view not autofillable - has multiline input type"); + return false; + } + return true; + } + private boolean isPackageFullyAllowedOrDeniedForAutofill( @NonNull String listString, @NonNull String packageName) { // If "PackageName:;" is in the string, then it the package is fully denied or allowed for @@ -1103,6 +1123,9 @@ public final class AutofillManager { } if (view instanceof EditText) { + if (mShouldEnableMultilineFilter && !isPassingMultilineCheck((EditText) view)) { + return false; + } return isPassingImeActionCheck((EditText) view); } diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java index 3df09c24ca30..1f0e95ea305a 100644 --- a/core/java/android/widget/AnalogClock.java +++ b/core/java/android/widget/AnalogClock.java @@ -49,15 +49,18 @@ import java.util.Formatter; import java.util.Locale; /** - * This widget display an analogic clock with two hands for hours and - * minutes. + * This widget displays an analogic clock with two hands for hours and minutes. * * @attr ref android.R.styleable#AnalogClock_dial * @attr ref android.R.styleable#AnalogClock_hand_hour * @attr ref android.R.styleable#AnalogClock_hand_minute * @attr ref android.R.styleable#AnalogClock_hand_second * @attr ref android.R.styleable#AnalogClock_timeZone - * @deprecated This widget is no longer supported. + * @deprecated This widget is no longer supported; except for + * {@link android.widget.RemoteViews} use cases like + * <a href="https://developer.android.com/develop/ui/views/appwidgets/overview"> + * app widgets</a>. + * */ @RemoteView @Deprecated diff --git a/core/java/android/window/IDumpCallback.aidl b/core/java/android/window/IDumpCallback.aidl new file mode 100644 index 000000000000..4c825d43add1 --- /dev/null +++ b/core/java/android/window/IDumpCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.window; + +/** + * Callback for processes which need to feed data to another process when it dumps. + * @hide + */ + interface IDumpCallback { + oneway void onDump(in ParcelFileDescriptor outFd); + }
\ No newline at end of file diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java index d8e64d48f456..95451a966055 100644 --- a/core/java/android/window/ScreenCapture.java +++ b/core/java/android/window/ScreenCapture.java @@ -309,7 +309,7 @@ public class ScreenCapture { /** Release any layers if set using {@link Builder#setExcludeLayers(SurfaceControl[])}. */ public void release() { - if (mExcludeLayers.length == 0) { + if (mExcludeLayers == null || mExcludeLayers.length == 0) { return; } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 75e797b4ec9d..44d517ad0c32 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -19,6 +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.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -212,9 +213,7 @@ public class IntentForwarderActivity extends Activity { buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom()); ((TextView) findViewById(R.id.open_cross_profile)).setText( - getResources().getString( - R.string.miniresolver_open_in_work, - target.loadLabel(packageManagerForTargetUser))); + getOpenInWorkMessage(target.loadLabel(packageManagerForTargetUser))); // The mini-resolver's negative button is reused in this flow to cancel the intent ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel); @@ -226,6 +225,13 @@ 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), + targetLabel); + } + private String getForwardToPersonalMessage() { return getSystemService(DevicePolicyManager.class).getResources().getString( FORWARD_INTENT_TO_PERSONAL, diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 73c5207e6238..499d38c31b59 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -19,6 +19,10 @@ package com.android.internal.app; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; 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_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_IN_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_USE_PERSONAL_BROWSER; +import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_USE_WORK_BROWSER; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; @@ -46,6 +50,7 @@ import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -1713,14 +1718,29 @@ public class ResolverActivity extends Activity implements } }.execute(); - ((TextView) findViewById(R.id.open_cross_profile)).setText( - getResources().getString( - inWorkProfile ? R.string.miniresolver_open_in_personal - : R.string.miniresolver_open_in_work, - otherProfileResolveInfo.getDisplayLabel())); - ((Button) findViewById(R.id.use_same_profile_browser)).setText( - inWorkProfile ? R.string.miniresolver_use_work_browser - : R.string.miniresolver_use_personal_browser); + CharSequence targetDisplayLabel = otherProfileResolveInfo.getDisplayLabel(); + + DevicePolicyResourcesManager devicePolicyResourcesManager = getSystemService( + DevicePolicyManager.class).getResources(); + + if (inWorkProfile) { + ((TextView) findViewById(R.id.open_cross_profile)).setText( + devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK, + () -> getString(R.string.miniresolver_open_in_work, targetDisplayLabel), + targetDisplayLabel)); + ((Button) findViewById(R.id.use_same_profile_browser)).setText( + devicePolicyResourcesManager.getString(MINIRESOLVER_USE_WORK_BROWSER, + () -> getString(R.string.miniresolver_use_work_browser))); + } else { + ((TextView) findViewById(R.id.open_cross_profile)).setText( + devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL, + () -> getString(R.string.miniresolver_open_in_personal, + targetDisplayLabel), + targetDisplayLabel)); + ((Button) findViewById(R.id.use_same_profile_browser)).setText( + devicePolicyResourcesManager.getString(MINIRESOLVER_USE_PERSONAL_BROWSER, + () -> getString(R.string.miniresolver_use_personal_browser))); + } findViewById(R.id.use_same_profile_browser).setOnClickListener( v -> { diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java index 8e7fe18b222b..098bce14e619 100644 --- a/core/java/com/android/internal/widget/MessagingImageMessage.java +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -198,6 +198,11 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage @Override public int getMeasuredType() { + if (mDrawable == null) { + Log.e(TAG, "getMeasuredType() after recycle()!"); + return MEASURED_NORMAL; + } + int measuredHeight = getMeasuredHeight(); int minImageHeight; if (mIsIsolated) { diff --git a/core/proto/android/app/notification_channel.proto b/core/proto/android/app/notification_channel.proto index c835b90ec969..d79de5cc4b2e 100644 --- a/core/proto/android/app/notification_channel.proto +++ b/core/proto/android/app/notification_channel.proto @@ -56,7 +56,9 @@ message NotificationChannelProto { optional android.media.AudioAttributesProto audio_attributes = 16; // If this is a blockable system notification channel. optional bool is_blockable_system = 17; - optional bool fg_service_shown = 18; + // On U+, this field will be true if either a foreground service or a user initiated job is + // shown whereas on T-, this field will only be true if a foreground service is shown. + optional bool user_visible_task_shown = 18; // Default is true. // Allows the notifications to appear outside of the shade in floating windows optional bool allow_app_overlay = 19; diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto index 9e53a9162429..25985ebc551a 100644 --- a/core/proto/android/server/windowmanagertransitiontrace.proto +++ b/core/proto/android/server/windowmanagertransitiontrace.proto @@ -23,7 +23,7 @@ import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; option java_multiple_files = true; /* Represents a file full of transition entries. - Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such + Encoded, it should start with 0x09 0x54 0x52 0x4E 0x54 0x52 0x41 0x43 0x45 (TRNTRACE), such that it can be easily identified. */ message TransitionTraceProto { diff --git a/core/res/res/drawable-hdpi/pointer_copy.png b/core/res/res/drawable-hdpi/pointer_copy.png Binary files differindex c5eda2e5b5c4..5d06a8eaa551 100644 --- a/core/res/res/drawable-hdpi/pointer_copy.png +++ b/core/res/res/drawable-hdpi/pointer_copy.png diff --git a/core/res/res/drawable-hdpi/pointer_grab.png b/core/res/res/drawable-hdpi/pointer_grab.png Binary files differindex 26da04de1d12..b76ec16e243b 100644 --- a/core/res/res/drawable-hdpi/pointer_grab.png +++ b/core/res/res/drawable-hdpi/pointer_grab.png diff --git a/core/res/res/drawable-hdpi/pointer_grabbing.png b/core/res/res/drawable-hdpi/pointer_grabbing.png Binary files differindex f4031a9227c7..10013e9a1821 100644 --- a/core/res/res/drawable-hdpi/pointer_grabbing.png +++ b/core/res/res/drawable-hdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-hdpi/pointer_hand.png b/core/res/res/drawable-hdpi/pointer_hand.png Binary files differindex a7ae55fc4b5f..8a7277477ab0 100644 --- a/core/res/res/drawable-hdpi/pointer_hand.png +++ b/core/res/res/drawable-hdpi/pointer_hand.png diff --git a/core/res/res/drawable-hdpi/pointer_nodrop.png b/core/res/res/drawable-hdpi/pointer_nodrop.png Binary files differindex 7043323701dd..9df140c5816b 100644 --- a/core/res/res/drawable-hdpi/pointer_nodrop.png +++ b/core/res/res/drawable-hdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png Binary files differindex e731108370d3..7189dada1bf2 100644 --- a/core/res/res/drawable-mdpi/pointer_copy.png +++ b/core/res/res/drawable-mdpi/pointer_copy.png diff --git a/core/res/res/drawable-mdpi/pointer_copy_large.png b/core/res/res/drawable-mdpi/pointer_copy_large.png Binary files differindex 15ccb04b8f76..a3d487df9e5e 100644 --- a/core/res/res/drawable-mdpi/pointer_copy_large.png +++ b/core/res/res/drawable-mdpi/pointer_copy_large.png diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png Binary files differindex d625b55f7066..977b36cd875a 100644 --- a/core/res/res/drawable-mdpi/pointer_grab.png +++ b/core/res/res/drawable-mdpi/pointer_grab.png diff --git a/core/res/res/drawable-mdpi/pointer_grab_large.png b/core/res/res/drawable-mdpi/pointer_grab_large.png Binary files differindex 9d36df0d6d94..80587ceb8aba 100644 --- a/core/res/res/drawable-mdpi/pointer_grab_large.png +++ b/core/res/res/drawable-mdpi/pointer_grab_large.png diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png Binary files differindex 71bb17ba5592..2bdcbdc7af75 100644 --- a/core/res/res/drawable-mdpi/pointer_grabbing.png +++ b/core/res/res/drawable-mdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-mdpi/pointer_grabbing_large.png b/core/res/res/drawable-mdpi/pointer_grabbing_large.png Binary files differindex 5574b07faf44..a8a599c5ebee 100644 --- a/core/res/res/drawable-mdpi/pointer_grabbing_large.png +++ b/core/res/res/drawable-mdpi/pointer_grabbing_large.png diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png Binary files differindex d7f7beda111c..e94b927cada6 100644 --- a/core/res/res/drawable-mdpi/pointer_hand.png +++ b/core/res/res/drawable-mdpi/pointer_hand.png diff --git a/core/res/res/drawable-mdpi/pointer_hand_large.png b/core/res/res/drawable-mdpi/pointer_hand_large.png Binary files differindex f775464ced18..7d89067b98bd 100644 --- a/core/res/res/drawable-mdpi/pointer_hand_large.png +++ b/core/res/res/drawable-mdpi/pointer_hand_large.png diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png Binary files differindex 931b74094d79..15764fa4e51b 100644 --- a/core/res/res/drawable-mdpi/pointer_nodrop.png +++ b/core/res/res/drawable-mdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-mdpi/pointer_nodrop_large.png b/core/res/res/drawable-mdpi/pointer_nodrop_large.png Binary files differindex 88f77d300a15..46ff5f779cef 100644 --- a/core/res/res/drawable-mdpi/pointer_nodrop_large.png +++ b/core/res/res/drawable-mdpi/pointer_nodrop_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png Binary files differindex 5b6cc5bc454f..8d889e1ad131 100644 --- a/core/res/res/drawable-xhdpi/pointer_copy.png +++ b/core/res/res/drawable-xhdpi/pointer_copy.png diff --git a/core/res/res/drawable-xhdpi/pointer_copy_large.png b/core/res/res/drawable-xhdpi/pointer_copy_large.png Binary files differindex d78a410a2887..860c85499617 100644 --- a/core/res/res/drawable-xhdpi/pointer_copy_large.png +++ b/core/res/res/drawable-xhdpi/pointer_copy_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png Binary files differindex 46dd3eeb95c6..dd3c6de46be5 100644 --- a/core/res/res/drawable-xhdpi/pointer_grab.png +++ b/core/res/res/drawable-xhdpi/pointer_grab.png diff --git a/core/res/res/drawable-xhdpi/pointer_grab_large.png b/core/res/res/drawable-xhdpi/pointer_grab_large.png Binary files differindex 1c7e63e2e527..bcae2c943d12 100644 --- a/core/res/res/drawable-xhdpi/pointer_grab_large.png +++ b/core/res/res/drawable-xhdpi/pointer_grab_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png Binary files differindex 2fb8a9c4442f..500fa73f58e4 100644 --- a/core/res/res/drawable-xhdpi/pointer_grabbing.png +++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png Binary files differindex 3467a03e2a56..c9c6f187e2a8 100644 --- a/core/res/res/drawable-xhdpi/pointer_grabbing_large.png +++ b/core/res/res/drawable-xhdpi/pointer_grabbing_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png Binary files differindex 926310cce74d..e931afe4eec8 100644 --- a/core/res/res/drawable-xhdpi/pointer_hand.png +++ b/core/res/res/drawable-xhdpi/pointer_hand.png diff --git a/core/res/res/drawable-xhdpi/pointer_hand_large.png b/core/res/res/drawable-xhdpi/pointer_hand_large.png Binary files differindex 546b222164b5..39ae1b7438d0 100644 --- a/core/res/res/drawable-xhdpi/pointer_hand_large.png +++ b/core/res/res/drawable-xhdpi/pointer_hand_large.png diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png Binary files differindex fdfc2671255c..4c9fe63136a8 100644 --- a/core/res/res/drawable-xhdpi/pointer_nodrop.png +++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png Binary files differindex 2b5e8a4b0f59..ae60e77bbc5e 100644 --- a/core/res/res/drawable-xhdpi/pointer_nodrop_large.png +++ b/core/res/res/drawable-xhdpi/pointer_nodrop_large.png diff --git a/core/res/res/drawable-xxhdpi/pointer_copy.png b/core/res/res/drawable-xxhdpi/pointer_copy.png Binary files differindex bef4fb4e8c17..24661a653f42 100644 --- a/core/res/res/drawable-xxhdpi/pointer_copy.png +++ b/core/res/res/drawable-xxhdpi/pointer_copy.png diff --git a/core/res/res/drawable-xxhdpi/pointer_grab.png b/core/res/res/drawable-xxhdpi/pointer_grab.png Binary files differindex 6caa1ba77eb9..1917722fa4fa 100644 --- a/core/res/res/drawable-xxhdpi/pointer_grab.png +++ b/core/res/res/drawable-xxhdpi/pointer_grab.png diff --git a/core/res/res/drawable-xxhdpi/pointer_grabbing.png b/core/res/res/drawable-xxhdpi/pointer_grabbing.png Binary files differindex b52f75174a16..a4cd9e56ba2f 100644 --- a/core/res/res/drawable-xxhdpi/pointer_grabbing.png +++ b/core/res/res/drawable-xxhdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-xxhdpi/pointer_hand.png b/core/res/res/drawable-xxhdpi/pointer_hand.png Binary files differindex f3ee077b5cc9..917529b9aa2a 100644 --- a/core/res/res/drawable-xxhdpi/pointer_hand.png +++ b/core/res/res/drawable-xxhdpi/pointer_hand.png diff --git a/core/res/res/drawable-xxhdpi/pointer_nodrop.png b/core/res/res/drawable-xxhdpi/pointer_nodrop.png Binary files differindex ef54301e4eac..35b4d13bd0be 100644 --- a/core/res/res/drawable-xxhdpi/pointer_nodrop.png +++ b/core/res/res/drawable-xxhdpi/pointer_nodrop.png diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml index da32939b5b0f..7e7c0caf6d78 100644 --- a/core/res/res/drawable/pointer_copy_icon.xml +++ b/core/res/res/drawable/pointer_copy_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_copy" - android:hotSpotX="7.5dp" + android:hotSpotX="8.5dp" android:hotSpotY="7.5dp" /> diff --git a/core/res/res/drawable/pointer_copy_large_icon.xml b/core/res/res/drawable/pointer_copy_large_icon.xml index 55d47b4f4d63..54e95238f501 100644 --- a/core/res/res/drawable/pointer_copy_large_icon.xml +++ b/core/res/res/drawable/pointer_copy_large_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_copy_large" - android:hotSpotX="22.5dp" - android:hotSpotY="22.5dp" /> + android:hotSpotX="21.25dp" + android:hotSpotY="18.75dp" /> diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml index b3d4e78828cd..dd1216a2995b 100644 --- a/core/res/res/drawable/pointer_grab_icon.xml +++ b/core/res/res/drawable/pointer_grab_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_grab" - android:hotSpotX="13.5dp" - android:hotSpotY="13.5dp" /> + android:hotSpotX="9.5dp" + android:hotSpotY="4.5dp" /> diff --git a/core/res/res/drawable/pointer_grab_large_icon.xml b/core/res/res/drawable/pointer_grab_large_icon.xml index 343c7d27c749..b5dbd11fb286 100644 --- a/core/res/res/drawable/pointer_grab_large_icon.xml +++ b/core/res/res/drawable/pointer_grab_large_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_grab_large" - android:hotSpotX="33.75dp" - android:hotSpotY="33.75dp" /> + android:hotSpotX="23.75dp" + android:hotSpotY="11.25dp" /> diff --git a/core/res/res/drawable/pointer_grabbing_large_icon.xml b/core/res/res/drawable/pointer_grabbing_large_icon.xml index ac1626530778..b041c8397771 100644 --- a/core/res/res/drawable/pointer_grabbing_large_icon.xml +++ b/core/res/res/drawable/pointer_grabbing_large_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_grabbing_large" - android:hotSpotX="25.5dp" - android:hotSpotY="22.5dp" /> + android:hotSpotX="21.25dp" + android:hotSpotY="18.75dp" /> diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml index 3f9d1a639ddb..0feccd296406 100644 --- a/core/res/res/drawable/pointer_hand_icon.xml +++ b/core/res/res/drawable/pointer_hand_icon.xml @@ -2,4 +2,4 @@ <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_hand" android:hotSpotX="9.5dp" - android:hotSpotY="1.5dp" /> + android:hotSpotY="2.5dp" /> diff --git a/core/res/res/drawable/pointer_hand_large_icon.xml b/core/res/res/drawable/pointer_hand_large_icon.xml index cd49762bd23f..6257b0ba2fb1 100644 --- a/core/res/res/drawable/pointer_hand_large_icon.xml +++ b/core/res/res/drawable/pointer_hand_large_icon.xml @@ -2,4 +2,4 @@ <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_hand_large" android:hotSpotX="23.75dp" - android:hotSpotY="3.75dp" /> + android:hotSpotY="6.25dp" /> diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml index 4dffd23638da..fb78d7469120 100644 --- a/core/res/res/drawable/pointer_nodrop_icon.xml +++ b/core/res/res/drawable/pointer_nodrop_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_nodrop" - android:hotSpotX="7.5dp" + android:hotSpotX="8.5dp" android:hotSpotY="7.5dp" /> diff --git a/core/res/res/drawable/pointer_nodrop_large_icon.xml b/core/res/res/drawable/pointer_nodrop_large_icon.xml index 602c744401a9..2385e11109b0 100644 --- a/core/res/res/drawable/pointer_nodrop_large_icon.xml +++ b/core/res/res/drawable/pointer_nodrop_large_icon.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" android:bitmap="@drawable/pointer_nodrop_large" - android:hotSpotX="22.5dp" - android:hotSpotY="22.5dp" /> + android:hotSpotX="21.25dp" + android:hotSpotY="18.75dp" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 76bae8de304d..3ff63519572f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4461,7 +4461,8 @@ See android.credentials.CredentialManager --> - <string name="config_defaultCredentialProviderService" translatable="false"></string> + <string-array name="config_defaultCredentialProviderService" translatable="false"> + </string-array> <!-- The package name for the system's smartspace service. This service returns smartspace results. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9fc2ed16b0c1..7b582da836aa 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3754,7 +3754,7 @@ <java-symbol type="string" name="config_defaultAppPredictionService" /> <java-symbol type="string" name="config_defaultContentSuggestionsService" /> <java-symbol type="string" name="config_defaultCredentialManagerHybridService" /> - <java-symbol type="string" name="config_defaultCredentialProviderService" /> + <java-symbol type="array" name="config_defaultCredentialProviderService" /> <java-symbol type="string" name="config_defaultSearchUiService" /> <java-symbol type="string" name="config_defaultSmartspaceService" /> <java-symbol type="string" name="config_defaultWallpaperEffectsGenerationService" /> diff --git a/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java new file mode 100644 index 000000000000..e20258a625dd --- /dev/null +++ b/core/tests/coretests/src/android/provider/DeviceConfigServiceManagerTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Class that tests the APIs of DeviceConfigServiceManager.ServiceRegisterer. + */ +@Presubmit +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DeviceConfigServiceManagerTest { + + private static final String SERVICE_NAME = "device_config_updatable"; + private DeviceConfigServiceManager.ServiceRegisterer mRegisterer; + + @Before + public void setUp() { + mRegisterer = new DeviceConfigServiceManager.ServiceRegisterer(SERVICE_NAME); + } + + @Test + public void testGetOrThrow() throws DeviceConfigServiceManager.ServiceNotFoundException { + if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { + assertThat(mRegisterer.getOrThrow()).isNotNull(); + } else { + assertThrows(DeviceConfigServiceManager.ServiceNotFoundException.class, + mRegisterer::getOrThrow); + } + } + + @Test + public void testGet() { + assumeTrue(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + assertThat(mRegisterer.get()).isNotNull(); + } + + @Test + public void testTryGet() { + if (UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) { + assertThat(mRegisterer.tryGet()).isNotNull(); + } else { + assertThat(mRegisterer.tryGet()).isNull(); + } + } +} diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index 852edef544b8..f0ed6ee5cb67 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,4 +1,4 @@ xutan@google.com # Give submodule owners in shell resource approval -per-file res*/*/*.xml = hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com +per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 8f364b448bf2..026ea069419d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -47,6 +47,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.InstanceId; +import com.android.launcher3.icons.BubbleBadgeIconFactory; +import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; import com.android.wm.shell.common.bubbles.BubbleInfo; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java deleted file mode 100644 index 56b13b8dcd46..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.bubbles; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; - -import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.BitmapInfo; -import com.android.wm.shell.R; - -/** - * Factory for creating app badge icons that are shown on bubbles. - */ -public class BubbleBadgeIconFactory extends BaseIconFactory { - - public BubbleBadgeIconFactory(Context context) { - super(context, context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size)); - } - - /** - * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This - * will include the workprofile indicator on the badge if appropriate. - */ - BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) { - if (userBadgedAppIcon instanceof AdaptiveIconDrawable) { - AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon; - userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(), ad.getForeground()); - } - if (isImportantConversation) { - userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon); - } - Bitmap userBadgedBitmap = createIconBitmap( - userBadgedAppIcon, 1, MODE_WITH_SHADOW); - return createIconBitmap(userBadgedBitmap); - } - - private class CircularRingDrawable extends CircularAdaptiveIcon { - - final int mImportantConversationColor; - final int mRingWidth; - final Rect mInnerBounds = new Rect(); - - final Drawable mDr; - - CircularRingDrawable(Drawable dr) { - super(null, null); - mDr = dr; - mImportantConversationColor = mContext.getResources().getColor( - R.color.important_conversation, null); - mRingWidth = mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.importance_ring_stroke_width); - } - - @Override - public void draw(Canvas canvas) { - int save = canvas.save(); - canvas.clipPath(getIconMask()); - canvas.drawColor(mImportantConversationColor); - mInnerBounds.set(getBounds()); - mInnerBounds.inset(mRingWidth, mRingWidth); - canvas.translate(mInnerBounds.left, mInnerBounds.top); - mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height()); - mDr.draw(canvas); - canvas.restoreToCount(save); - } - } - - private static class CircularAdaptiveIcon extends AdaptiveIconDrawable { - - final Path mPath = new Path(); - - CircularAdaptiveIcon(Drawable bg, Drawable fg) { - super(bg, fg); - } - - @Override - public Path getIconMask() { - mPath.reset(); - Rect bounds = getBounds(); - mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW); - return mPath; - } - - @Override - public void draw(Canvas canvas) { - int save = canvas.save(); - canvas.clipPath(getIconMask()); - - Drawable d; - if ((d = getBackground()) != null) { - d.draw(canvas); - } - if ((d = getForeground()) != null) { - d.draw(canvas); - } - canvas.restoreToCount(save); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index da8eb479a77b..fd66153c24c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -89,6 +89,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; +import com.android.launcher3.icons.BubbleBadgeIconFactory; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; @@ -317,8 +320,13 @@ public class BubbleController implements ConfigurationChangeListener, mBubblePositioner = positioner; mBubbleData = data; mSavedUserBubbleData = new SparseArray<>(); - mBubbleIconFactory = new BubbleIconFactory(context); - mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context); + mBubbleIconFactory = new BubbleIconFactory(context, + context.getResources().getDimensionPixelSize(R.dimen.bubble_size)); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(context, + context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + context.getResources().getColor(R.color.important_conversation), + context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; mOneHandedOptional = oneHandedOptional; @@ -927,8 +935,13 @@ public class BubbleController implements ConfigurationChangeListener, if (mStackView != null) { mStackView.onThemeChanged(); } - mBubbleIconFactory = new BubbleIconFactory(mContext); - mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); + mBubbleIconFactory = new BubbleIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size)); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + mContext.getResources().getColor(R.color.important_conversation), + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)); // Reload each bubble for (Bubble b : mBubbleData.getBubbles()) { @@ -964,8 +977,13 @@ public class BubbleController implements ConfigurationChangeListener, mDensityDpi = newConfig.densityDpi; mScreenBounds.set(newConfig.windowConfiguration.getBounds()); mBubbleData.onMaxBubblesChanged(); - mBubbleIconFactory = new BubbleIconFactory(mContext); - mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext); + mBubbleIconFactory = new BubbleIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size)); + mBubbleBadgeIconFactory = new BubbleBadgeIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + mContext.getResources().getColor(R.color.important_conversation), + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)); mStackView.onDisplaySizeChanged(); } if (newConfig.fontScale != mFontScale) { @@ -1052,7 +1070,27 @@ public class BubbleController implements ConfigurationChangeListener, * Expands and selects the provided bubble as long as it already exists in the stack or the * overflow. * - * This is currently only used when opening a bubble via clicking on a conversation widget. + * This is used by external callers (launcher). + */ + public void expandStackAndSelectBubbleFromLauncher(String key) { + Bubble b = mBubbleData.getAnyBubbleWithkey(key); + if (b == null) { + return; + } + if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { + // already in the stack + mBubbleData.setSelectedBubbleFromLauncher(b); + mLayerView.showExpandedView(b); + } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { + // TODO: (b/271468319) handle overflow + } else { + Log.w(TAG, "didn't add bubble from launcher: " + key); + } + } + + /** + * Expands and selects the provided bubble as long as it already exists in the stack or the + * overflow. This is currently used when opening a bubble via clicking on a conversation widget. */ public void expandStackAndSelectBubble(Bubble b) { if (b == null) { @@ -1703,6 +1741,14 @@ public class BubbleController implements ConfigurationChangeListener, // Update the cached state for queries from SysUI mImpl.mCachedState.update(update); + + if (isShowingAsBubbleBar() && mBubbleStateListener != null) { + BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate(); + // Some updates aren't relevant to the bubble bar so check first. + if (bubbleBarUpdate.anythingChanged()) { + mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate); + } + } } }; @@ -1972,17 +2018,20 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void showBubble(String key, boolean onLauncherHome) { - // TODO + mMainExecutor.execute(() -> { + mBubblePositioner.setShowingInBubbleBar(onLauncherHome); + mController.expandStackAndSelectBubbleFromLauncher(key); + }); } @Override public void removeBubble(String key, int reason) { - // TODO + // TODO (b/271466616) allow removals from launcher } @Override public void collapseBubbles() { - // TODO + mMainExecutor.execute(() -> mController.collapseStack()); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index a26c0c487d19..f9cf9d34ec31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -334,6 +334,35 @@ public class BubbleData { dispatchPendingChanges(); } + /** + * Sets the selected bubble and expands it, but doesn't dispatch changes + * to {@link BubbleData.Listener}. This is used for updates coming from launcher whose views + * will already be updated so we don't need to notify them again, but BubbleData should be + * updated to have the correct state. + */ + public void setSelectedBubbleFromLauncher(BubbleViewProvider bubble) { + if (DEBUG_BUBBLE_DATA) { + Log.d(TAG, "setSelectedBubbleFromLauncher: " + bubble); + } + mExpanded = true; + if (Objects.equals(bubble, mSelectedBubble)) { + return; + } + boolean isOverflow = bubble != null && BubbleOverflow.KEY.equals(bubble.getKey()); + if (bubble != null + && !mBubbles.contains(bubble) + && !mOverflowBubbles.contains(bubble) + && !isOverflow) { + Log.e(TAG, "Cannot select bubble which doesn't exist!" + + " (" + bubble + ") bubbles=" + mBubbles); + return; + } + if (bubble != null && !isOverflow) { + ((Bubble) bubble).markAsAccessedAt(mTimeSource.currentTimeMillis()); + } + mSelectedBubble = bubble; + } + public void setSelectedBubble(BubbleViewProvider bubble) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "setSelectedBubble: " + bubble); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java deleted file mode 100644 index 4ded3ea951e5..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.wm.shell.bubbles; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.ShortcutInfo; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; - -import androidx.annotation.VisibleForTesting; - -import com.android.launcher3.icons.BaseIconFactory; -import com.android.wm.shell.R; - -/** - * Factory for creating normalized bubble icons. - * We are not using Launcher's IconFactory because bubbles only runs on the UI thread, - * so there is no need to manage a pool across multiple threads. - */ -@VisibleForTesting -public class BubbleIconFactory extends BaseIconFactory { - - public BubbleIconFactory(Context context) { - super(context, context.getResources().getConfiguration().densityDpi, - context.getResources().getDimensionPixelSize(R.dimen.bubble_size)); - } - - /** - * Returns the drawable that the developer has provided to display in the bubble. - */ - Drawable getBubbleDrawable(@NonNull final Context context, - @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) { - if (shortcutInfo != null) { - LauncherApps launcherApps = - (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); - int density = context.getResources().getConfiguration().densityDpi; - return launcherApps.getShortcutIconDrawable(shortcutInfo, density); - } else { - if (ic != null) { - if (ic.getType() == Icon.TYPE_URI - || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - ic.getUri(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - return ic.loadDrawable(context); - } - return null; - } - } - - /** - * Creates the bitmap for the provided drawable and returns the scale used for - * drawing the actual drawable. - */ - public Bitmap createIconBitmap(@NonNull Drawable icon, float[] outScale) { - if (outScale == null) { - outScale = new float[1]; - } - icon = normalizeAndWrapToAdaptiveIcon(icon, - true /* shrinkNonAdaptiveIcons */, - null /* outscale */, - outScale); - return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 6cdb80b2bb93..c2a05b715f44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -28,6 +28,7 @@ import android.util.PathParser import android.util.TypedValue import android.view.LayoutInflater import android.widget.FrameLayout +import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.R import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView @@ -93,7 +94,8 @@ class BubbleOverflow( val shapeColor = res.getColor(android.R.color.system_accent1_1000) overflowBtn?.iconDrawable?.setTint(shapeColor) - val iconFactory = BubbleIconFactory(context) + val iconFactory = BubbleIconFactory(context, + context.getResources().getDimensionPixelSize(R.dimen.bubble_size)) // Update bitmap val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 1a97c0504b37..d1081de11f30 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -42,6 +42,8 @@ import android.view.LayoutInflater; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.BubbleBadgeIconFactory; +import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS index 926cfb3b12ef..deb7c6db338f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS @@ -1,2 +1,3 @@ # WM shell sub-module desktop owners +atsjenk@google.com madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS index 0c2d5c49f830..ccbb9cf298a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS @@ -1,2 +1,3 @@ # WM shell sub-module freeform owners +atsjenk@google.com madym@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 318a49a8de31..6d46a9c3d0e7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; import static android.view.Display.DEFAULT_DISPLAY; import android.app.ActivityManager; @@ -310,8 +311,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { // TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible. if (mReverseDefaultRotationEnabled) { setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true, - /* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE}, - /* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE}); + /* fromOrientations */ + new int[]{SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_LANDSCAPE}, + /* toOrientations */ + new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE, + SCREEN_ORIENTATION_SENSOR_LANDSCAPE}); } else { setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true, /* fromOrientations */ null, /* toOrientations */ null); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java index 867162be4c6d..24d0b996a3cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java @@ -198,7 +198,7 @@ public class PipBoundsAlgorithm { /** * @return whether the given {@param aspectRatio} is valid. */ - private boolean isValidPictureInPictureAspectRatio(float aspectRatio) { + public boolean isValidPictureInPictureAspectRatio(float aspectRatio) { return Float.compare(mMinAspectRatio, aspectRatio) <= 0 && Float.compare(aspectRatio, mMaxAspectRatio) <= 0; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 09e050e91c64..a0bd064149d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1259,7 +1259,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) { if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(), mPictureInPictureParams.getAspectRatioFloat())) { - mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat()); + if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio( + params.getAspectRatioFloat())) { + mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat()); + } else { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: New aspect ratio is not valid." + + " hasAspectRatio=%b" + + " aspectRatio=%f", + TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat()); + } } if (mDeferredTaskInfo != null || PipUtils.remoteActionsChanged(params.getActions(), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 81e118a31b73..f819bee2d5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -129,9 +129,10 @@ interface ISplitScreen { /** * Start a pair of intents in one transition. */ - oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1, - in PendingIntent pendingIntent2, in Bundle options2, int splitPosition, - float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; + oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1, + in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2, + in Bundle options2, int splitPosition, float splitRatio, + in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; /** * Blocking call that notifies and gets additional split-screen targets when entering diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 7d5ab8428a3e..2cd16be9590c 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 @@ -626,6 +626,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, splitPosition, splitRatio, adapter, instanceId); } + private void startIntents(PendingIntent pendingIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2, + @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + Intent fillInIntent1 = null; + Intent fillInIntent2 = null; + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); + final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { + fillInIntent1 = new Intent(); + fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + fillInIntent2 = new Intent(); + fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + pendingIntent2 = null; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); + } + } + mStageCoordinator.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, + pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, + remoteTransition, instanceId); + } + @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -1066,11 +1095,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1, - PendingIntent pendingIntent2, @Nullable Bundle options2, + public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1, + @Nullable Bundle options1, PendingIntent pendingIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { - // TODO(b/259368992): To be implemented. + executeRemoteCallWithTaskPermission(mController, "startIntents", + (controller) -> + controller.startIntents(pendingIntent1, shortcutInfo1, + options1, pendingIntent2, shortcutInfo2, options2, + splitPosition, splitRatio, remoteTransition, instanceId) + ); } @Override 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 dd91a37039e4..f00fdba50748 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 @@ -682,6 +682,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setEnterInstanceId(instanceId); } + void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, Intent fillInIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, + @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (!mMainStage.isActive()) { + // Build a request WCT that will launch both apps such that task 0 is on the main stage + // while task 1 is on the side stage. + mMainStage.activate(wct, false /* reparent */); + } + + prepareEvictChildTasksIfSplitActive(wct); + mSplitLayout.setDivideRatio(splitRatio); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + setRootForceTranslucent(false, wct); + + setSideStagePosition(splitPosition, wct); + options1 = options1 != null ? options1 : new Bundle(); + addActivityOptions(options1, mSideStage); + if (shortcutInfo1 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); + } else { + wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + } + options2 = options2 != null ? options2 : new Bundle(); + addActivityOptions(options2, mMainStage); + if (shortcutInfo2 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2); + } else { + wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); + } + + mSplitTransitions.startEnterTransition( + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null); + setEnterInstanceId(instanceId); + } + /** Starts a pair of tasks using legacy transition. */ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index 1c28c3d58ccb..64dfc3ef845d 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -7,3 +7,4 @@ lbill@google.com madym@google.com hwwang@google.com chenghsiuchang@google.com +atsjenk@google.com diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 5d79104200d9..70c36a5803ee 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -55,6 +55,10 @@ cc_defaults { // GCC false-positives on this warning, and since we -Werror that's // a problem "-Wno-free-nonheap-object", + + // Do not de-optimise cold code paths in AFDO. + // Some code paths might be infrequently executed but critical to latency. + "-fno-profile-sample-accurate", ], include_dirs: [ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 8ea71f11e2f0..1f929685b62c 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -28,6 +28,7 @@ #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> +#include <SkPaintFilterCanvas.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkRect.h> @@ -36,15 +37,15 @@ #include <SkStream.h> #include <SkString.h> #include <SkTypeface.h> -#include "include/gpu/GpuTypes.h" // from Skia #include <android-base/properties.h> +#include <gui/TraceUtils.h> #include <unistd.h> #include <sstream> -#include <gui/TraceUtils.h> #include "LightingInfo.h" #include "VectorDrawable.h" +#include "include/gpu/GpuTypes.h" // from Skia #include "thread/CommonPool.h" #include "tools/SkSharingProc.h" #include "utils/Color.h" @@ -449,6 +450,23 @@ void SkiaPipeline::endCapture(SkSurface* surface) { } } +class ForceDitherCanvas : public SkPaintFilterCanvas { +public: + ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {} + +protected: + bool onFilter(SkPaint& paint) const override { + paint.setDither(true); + return true; + } + + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { + // We unroll the drawable using "this" canvas, so that draw calls contained inside will + // get dithering applied + drawable->draw(this, matrix); + } +}; + void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp<SkSurface> surface, @@ -503,6 +521,12 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip, canvas->clear(SK_ColorTRANSPARENT); } + std::optional<ForceDitherCanvas> forceDitherCanvas; + if (shouldForceDither()) { + forceDitherCanvas.emplace(canvas); + canvas = &forceDitherCanvas.value(); + } + if (1 == nodes.size()) { if (!nodes[0]->nothingToDraw()) { RenderNodeDrawable root(nodes[0].get(), canvas); diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index befee8989383..0763b06b53ef 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -98,6 +98,8 @@ protected: bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; } + virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; } + private: void renderFrameImpl(const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index c8f2e69ae0a4..6f1b99b95bbd 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -203,6 +203,11 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr return nullptr; } +bool SkiaVulkanPipeline::shouldForceDither() const { + if (mVkSurface && mVkSurface->isBeyond8Bit()) return false; + return SkiaPipeline::shouldForceDither(); +} + void SkiaVulkanPipeline::onContextDestroyed() { if (mVkSurface) { vulkanManager().destroySurface(mVkSurface); diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index d921ddb0d0fb..0713e93bccde 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -63,6 +63,8 @@ public: protected: void onContextDestroyed() override; + bool shouldForceDither() const override; + private: renderthread::VulkanManager& vulkanManager(); renderthread::VulkanSurface* mVkSurface = nullptr; diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index 21b6c44e997e..ae4f0572576e 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -530,6 +530,16 @@ void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) { } } +bool VulkanSurface::isBeyond8Bit() const { + switch (mWindowInfo.bufferFormat) { + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return true; + default: + return false; + } +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h index e2ddc6b07768..3b69b73bcab3 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -48,6 +48,8 @@ public: void setColorSpace(sk_sp<SkColorSpace> colorSpace); + bool isBeyond8Bit() const; + private: /* * All structs/methods in this private section are specifically for use by the VulkanManager diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index f9d4efea57e2..fe5afc5a717e 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -268,7 +268,7 @@ interface IAudioService { boolean isVolumeControlUsingVolumeGroups(); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") - oneway void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register); + void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") void setNotifAliasRingForTest(boolean alias); diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 9a4aa3344374..dea7f03d369a 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -43,6 +43,8 @@ #include <android_runtime/android_hardware_HardwareBuffer.h> +#include <android-base/stringprintf.h> + #include <binder/MemoryDealer.h> #include <cutils/compiler.h> @@ -1276,7 +1278,8 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { ALOGE("Could not create MediaCodec.BufferInfo."); env->ExceptionClear(); } - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + "Fatal error: could not create MediaCodec.BufferInfo object"); return; } @@ -1309,7 +1312,8 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { ALOGE("Could not create CodecException object."); env->ExceptionClear(); } - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + "Fatal error: could not create CodecException object"); return; } @@ -1322,7 +1326,9 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { CHECK(msg->findMessage("format", &format)); if (OK != ConvertMessageToMap(env, format, &obj)) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + "Fatal error: failed to convert format " + "from native to Java object"); return; } @@ -1353,7 +1359,8 @@ void JMediaCodec::handleFirstTunnelFrameReadyNotification(const sp<AMessage> &ms status_t err = ConvertMessageToMap(env, data, &obj); if (err != OK) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + "Fatal error: failed to convert format from native to Java object"); return; } @@ -1374,7 +1381,8 @@ void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) { status_t err = ConvertMessageToMap(env, data, &obj); if (err != OK) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + "Fatal error: failed to convert format from native to Java object"); return; } @@ -1385,6 +1393,18 @@ void JMediaCodec::handleFrameRenderedNotification(const sp<AMessage> &msg) { env->DeleteLocalRef(obj); } +std::string JMediaCodec::getExceptionMessage(const char *msg = nullptr) const { + if (mCodec == nullptr) { + return msg ?: ""; + } + std::string prefix = ""; + if (msg && msg[0] != '\0') { + prefix.append(msg); + prefix.append("\n"); + } + return prefix + mCodec->getErrorLog().extract(); +} + void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) { switch (msg->what()) { case kWhatCallbackNotify: @@ -1471,9 +1491,17 @@ static void throwCryptoException(JNIEnv *env, status_t err, const char *msg, env->Throw(exception); } +static std::string GetExceptionMessage(const sp<JMediaCodec> &codec, const char *msg) { + if (codec == NULL) { + return msg ?: "codec is released already"; + } + return codec->getExceptionMessage(msg); +} + static jint throwExceptionAsNecessary( JNIEnv *env, status_t err, int32_t actionCode = ACTION_CODE_FATAL, - const char *msg = NULL, const sp<ICrypto>& crypto = NULL) { + const char *msg = NULL, const sp<ICrypto>& crypto = NULL, + const sp<JMediaCodec> &codec = NULL) { switch (err) { case OK: return 0; @@ -1488,23 +1516,38 @@ static jint throwExceptionAsNecessary( return DEQUEUE_INFO_OUTPUT_BUFFERS_CHANGED; case INVALID_OPERATION: - jniThrowException(env, "java/lang/IllegalStateException", msg); + jniThrowException( + env, "java/lang/IllegalStateException", + GetExceptionMessage(codec, msg).c_str()); return 0; case BAD_VALUE: - jniThrowException(env, "java/lang/IllegalArgumentException", msg); + jniThrowException( + env, "java/lang/IllegalArgumentException", + GetExceptionMessage(codec, msg).c_str()); return 0; default: if (isCryptoError(err)) { - throwCryptoException(env, err, msg, crypto); + throwCryptoException( + env, err, + GetExceptionMessage(codec, msg).c_str(), + crypto); return 0; } - throwCodecException(env, err, actionCode, msg); + throwCodecException( + env, err, actionCode, + GetExceptionMessage(codec, msg).c_str()); return 0; } } +static jint throwExceptionAsNecessary( + JNIEnv *env, status_t err, const sp<JMediaCodec> &codec, + int32_t actionCode = ACTION_CODE_FATAL) { + return throwExceptionAsNecessary(env, err, actionCode, NULL, NULL, codec); +} + static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListener( JNIEnv *env, jobject thiz, @@ -1512,13 +1555,13 @@ static void android_media_MediaCodec_native_enableOnFirstTunnelFrameReadyListene sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->enableOnFirstTunnelFrameReadyListener(enabled); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_native_enableOnFrameRenderedListener( @@ -1528,13 +1571,13 @@ static void android_media_MediaCodec_native_enableOnFrameRenderedListener( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->enableOnFrameRenderedListener(enabled); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_native_setCallback( @@ -1544,13 +1587,13 @@ static void android_media_MediaCodec_native_setCallback( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->setCallback(cb); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_native_configure( @@ -1564,7 +1607,7 @@ static void android_media_MediaCodec_native_configure( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -1602,7 +1645,7 @@ static void android_media_MediaCodec_native_configure( err = codec->configure(format, bufferProducer, crypto, descrambler, flags); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_native_setSurface( @@ -1612,7 +1655,7 @@ static void android_media_MediaCodec_native_setSurface( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -1631,7 +1674,7 @@ static void android_media_MediaCodec_native_setSurface( } status_t err = codec->setSurface(bufferProducer); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } sp<PersistentSurface> android_media_MediaCodec_getPersistentInputSurface( @@ -1735,7 +1778,7 @@ static void android_media_MediaCodec_setInputSurface( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -1749,7 +1792,7 @@ static void android_media_MediaCodec_setInputSurface( } status_t err = codec->setInputSurface(persistentSurface); if (err != NO_ERROR) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } } @@ -1759,7 +1802,7 @@ static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env, sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -1767,7 +1810,7 @@ static jobject android_media_MediaCodec_createInputSurface(JNIEnv* env, sp<IGraphicBufferProducer> bufferProducer; status_t err = codec->createInputSurface(&bufferProducer); if (err != NO_ERROR) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); return NULL; } @@ -1782,13 +1825,13 @@ static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->start(); - throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, "start failed"); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) { @@ -1797,13 +1840,13 @@ static void android_media_MediaCodec_stop(JNIEnv *env, jobject thiz) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->stop(); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) { @@ -1812,7 +1855,7 @@ static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -1825,7 +1868,7 @@ static void android_media_MediaCodec_reset(JNIEnv *env, jobject thiz) { // trigger an IllegalStateException. err = UNKNOWN_ERROR; } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) { @@ -1834,13 +1877,13 @@ static void android_media_MediaCodec_flush(JNIEnv *env, jobject thiz) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->flush(); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_queueInputBuffer( @@ -1856,7 +1899,7 @@ static void android_media_MediaCodec_queueInputBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -1866,7 +1909,8 @@ static void android_media_MediaCodec_queueInputBuffer( index, offset, size, timestampUs, flags, &errorDetailMsg); throwExceptionAsNecessary( - env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str()); + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str()); } struct NativeCryptoInfo { @@ -1890,7 +1934,9 @@ struct NativeCryptoInfo { } else if (jmode == gCryptoModes.AesCbc) { mMode = CryptoPlugin::kMode_AES_CBC; } else { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + base::StringPrintf("unrecognized crypto mode: %d", jmode).c_str()); return; } @@ -2026,7 +2072,7 @@ static void android_media_MediaCodec_queueSecureInputBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -2056,7 +2102,9 @@ static void android_media_MediaCodec_queueSecureInputBuffer( } else if (jmode == gCryptoModes.AesCbc) { mode = CryptoPlugin::kMode_AES_CBC; } else { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + base::StringPrintf("Unrecognized crypto mode: %d", jmode).c_str()); return; } @@ -2175,8 +2223,8 @@ static void android_media_MediaCodec_queueSecureInputBuffer( subSamples = NULL; throwExceptionAsNecessary( - env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str(), - codec->getCrypto()); + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto()); } static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) { @@ -2518,14 +2566,16 @@ static void android_media_MediaCodec_native_queueLinearBlock( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == nullptr || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } sp<AMessage> tunings; status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); if (err != OK) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + "error occurred while converting tunings from Java to native"); return; } @@ -2545,15 +2595,23 @@ static void android_media_MediaCodec_native_queueLinearBlock( } env->MonitorExit(lock.get()); } else { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + "Failed to grab lock for a LinearBlock object"); return; } AString errorDetailMsg; if (codec->hasCryptoOrDescrambler()) { if (!memory) { + // It means there was an unexpected failure in extractMemoryFromContext above ALOGI("queueLinearBlock: no ashmem memory for encrypted content"); - throwExceptionAsNecessary(env, BAD_VALUE); + throwExceptionAsNecessary( + env, BAD_VALUE, ACTION_CODE_FATAL, + "Unexpected error: the input buffer is not compatible with " + "the secure codec, and a fallback logic failed.\n" + "Suggestion: please try including the secure codec when calling " + "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer."); return; } auto cryptoInfo = @@ -2577,14 +2635,22 @@ static void android_media_MediaCodec_native_queueLinearBlock( ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err); } else { if (!buffer) { + // It means there was an unexpected failure in extractBufferFromContext above ALOGI("queueLinearBlock: no C2Buffer found"); - throwExceptionAsNecessary(env, BAD_VALUE); + throwExceptionAsNecessary( + env, BAD_VALUE, ACTION_CODE_FATAL, + "Unexpected error: the input buffer is not compatible with " + "the non-secure codec, and a fallback logic failed.\n" + "Suggestion: please do not include the secure codec when calling " + "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer."); return; } err = codec->queueBuffer( index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); } - throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str()); } static void android_media_MediaCodec_native_queueHardwareBuffer( @@ -2595,14 +2661,16 @@ static void android_media_MediaCodec_native_queueHardwareBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } sp<AMessage> tunings; status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings); if (err != OK) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + "error occurred while converting tunings from Java to native"); return; } @@ -2627,7 +2695,9 @@ static void android_media_MediaCodec_native_queueHardwareBuffer( ALOGW("Failed to wrap AHardwareBuffer into C2GraphicAllocation"); native_handle_close(handle); native_handle_delete(handle); - throwExceptionAsNecessary(env, BAD_VALUE); + throwExceptionAsNecessary( + env, BAD_VALUE, ACTION_CODE_FATAL, + "HardwareBuffer not recognized"); return; } std::shared_ptr<C2GraphicBlock> block = _C2BlockFactory::CreateGraphicBlock(alloc); @@ -2636,7 +2706,9 @@ static void android_media_MediaCodec_native_queueHardwareBuffer( AString errorDetailMsg; err = codec->queueBuffer( index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg); - throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str()); + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str()); } static void android_media_MediaCodec_native_getOutputFrame( @@ -2646,13 +2718,13 @@ static void android_media_MediaCodec_native_getOutputFrame( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->getOutputFrame(env, frame, index); if (err != OK) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } } @@ -2663,7 +2735,7 @@ static jint android_media_MediaCodec_dequeueInputBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return -1; } @@ -2674,7 +2746,7 @@ static jint android_media_MediaCodec_dequeueInputBuffer( return (jint) index; } - return throwExceptionAsNecessary(env, err); + return throwExceptionAsNecessary(env, err, codec); } static jint android_media_MediaCodec_dequeueOutputBuffer( @@ -2684,7 +2756,7 @@ static jint android_media_MediaCodec_dequeueOutputBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return 0; } @@ -2696,7 +2768,7 @@ static jint android_media_MediaCodec_dequeueOutputBuffer( return (jint) index; } - return throwExceptionAsNecessary(env, err); + return throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_releaseOutputBuffer( @@ -2707,13 +2779,13 @@ static void android_media_MediaCodec_releaseOutputBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->releaseOutputBuffer(index, render, updatePTS, timestampNs); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env, @@ -2722,13 +2794,13 @@ static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env, sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t err = codec->signalEndOfInputStream(); - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static jobject android_media_MediaCodec_getFormatNative( @@ -2738,7 +2810,7 @@ static jobject android_media_MediaCodec_getFormatNative( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2749,7 +2821,7 @@ static jobject android_media_MediaCodec_getFormatNative( return format; } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); return NULL; } @@ -2761,7 +2833,7 @@ static jobject android_media_MediaCodec_getOutputFormatForIndexNative( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2772,7 +2844,7 @@ static jobject android_media_MediaCodec_getOutputFormatForIndexNative( return format; } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); return NULL; } @@ -2784,7 +2856,7 @@ static jobjectArray android_media_MediaCodec_getBuffers( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2797,7 +2869,7 @@ static jobjectArray android_media_MediaCodec_getBuffers( // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } return NULL; @@ -2810,7 +2882,7 @@ static jobject android_media_MediaCodec_getBuffer( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2823,7 +2895,7 @@ static jobject android_media_MediaCodec_getBuffer( // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } return NULL; @@ -2836,7 +2908,7 @@ static jobject android_media_MediaCodec_getImage( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2849,7 +2921,7 @@ static jobject android_media_MediaCodec_getImage( // if we're out of memory, an exception was already thrown if (err != NO_MEMORY) { - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } return NULL; @@ -2862,7 +2934,7 @@ static jobject android_media_MediaCodec_getName( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2873,7 +2945,7 @@ static jobject android_media_MediaCodec_getName( return name; } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); return NULL; } @@ -2885,7 +2957,7 @@ static jobject android_media_MediaCodec_getOwnCodecInfo( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -2896,7 +2968,7 @@ static jobject android_media_MediaCodec_getOwnCodecInfo( return codecInfoObj; } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); return NULL; } @@ -2908,7 +2980,8 @@ android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz) sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - jniThrowException(env, "java/lang/IllegalStateException", NULL); + jniThrowException(env, "java/lang/IllegalStateException", + GetExceptionMessage(codec, NULL).c_str()); return 0; } @@ -2937,7 +3010,7 @@ static void android_media_MediaCodec_setParameters( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -2948,7 +3021,7 @@ static void android_media_MediaCodec_setParameters( err = codec->setParameters(params); } - throwExceptionAsNecessary(env, err); + throwExceptionAsNecessary(env, err, codec); } static void android_media_MediaCodec_setVideoScalingMode( @@ -2956,13 +3029,14 @@ static void android_media_MediaCodec_setVideoScalingMode( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } if (mode != NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW && mode != NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + jniThrowException(env, "java/lang/IllegalArgumentException", + String8::format("Unrecognized mode: %d", mode)); return; } @@ -2974,7 +3048,7 @@ static void android_media_MediaCodec_setAudioPresentation( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } @@ -2986,14 +3060,14 @@ static jobject android_media_MediaCodec_getSupportedVendorParameters( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } jobject ret = NULL; status_t status = codec->querySupportedVendorParameters(env, &ret); if (status != OK) { - throwExceptionAsNecessary(env, status); + throwExceptionAsNecessary(env, status, codec); } return ret; @@ -3004,7 +3078,7 @@ static jobject android_media_MediaCodec_getParameterDescriptor( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return NULL; } @@ -3021,13 +3095,13 @@ static void android_media_MediaCodec_subscribeToVendorParameters( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t status = codec->subscribeToVendorParameters(env, names); if (status != OK) { - throwExceptionAsNecessary(env, status); + throwExceptionAsNecessary(env, status, codec); } return; } @@ -3037,13 +3111,13 @@ static void android_media_MediaCodec_unsubscribeFromVendorParameters( sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL || codec->initCheck() != OK) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); return; } status_t status = codec->unsubscribeFromVendorParameters(env, names); if (status != OK) { - throwExceptionAsNecessary(env, status); + throwExceptionAsNecessary(env, status, codec); } return; } @@ -3440,11 +3514,15 @@ static jobject android_media_MediaCodec_LinearBlock_native_map( if (!context->mReadonlyMapping) { const C2BufferData data = buffer->data(); if (data.type() != C2BufferData::LINEAR) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + "Underlying buffer is not a linear buffer"); return nullptr; } if (data.linearBlocks().size() != 1u) { - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + "Underlying buffer contains more than one block"); return nullptr; } C2ConstLinearBlock block = data.linearBlocks().front(); @@ -3492,7 +3570,9 @@ static jobject android_media_MediaCodec_LinearBlock_native_map( false, // readOnly true /* clearBuffer */); } - throwExceptionAsNecessary(env, INVALID_OPERATION); + throwExceptionAsNecessary( + env, INVALID_OPERATION, ACTION_CODE_FATAL, + "Underlying buffer is empty"); return nullptr; } @@ -3515,7 +3595,9 @@ static void PopulateNamesVector( } const char *cstr = env->GetStringUTFChars(jstr, nullptr); if (cstr == nullptr) { - throwExceptionAsNecessary(env, BAD_VALUE); + throwExceptionAsNecessary( + env, BAD_VALUE, ACTION_CODE_FATAL, + "Error converting Java string to native"); return; } names->emplace_back(cstr); @@ -3567,6 +3649,7 @@ static jboolean android_media_MediaCodec_LinearBlock_checkCompatible( } status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible); if (err != OK) { + // TODO: CodecErrorLog throwExceptionAsNecessary(env, err); } return isCompatible; diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 616c31b29157..fbaf64fda572 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -176,6 +176,8 @@ struct JMediaCodec : public AHandler { const sp<ICrypto> &getCrypto() { return mCrypto; } + std::string getExceptionMessage(const char *msg) const; + protected: virtual ~JMediaCodec(); diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 6f5015d6c79b..24f92c00c772 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -5,7 +5,7 @@ * 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 + * http://www.apache.org/licenses/LICENSE-2.0N * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -74,7 +74,6 @@ class CredentialSelectorActivity : ComponentActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) - Log.d(Constants.LOG_TAG, "Existing activity received new intent") try { val viewModel: CredentialSelectorViewModel by viewModels() val (isCancellationRequest, shouldShowCancellationUi, appDisplayName) = 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 edc902e41e9a..53731f06816a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import com.android.compose.rememberSystemUiController import com.android.credentialmanager.common.material.ModalBottomSheetLayout import com.android.credentialmanager.common.material.ModalBottomSheetValue import com.android.credentialmanager.common.material.rememberModalBottomSheetState @@ -38,6 +39,12 @@ fun ModalBottomSheet( initialValue = ModalBottomSheetValue.Expanded, skipHalfExpanded = true ) + val sysUiController = rememberSystemUiController() + if (state.targetValue == ModalBottomSheetValue.Hidden) { + setTransparentSystemBarsColor(sysUiController) + } else { + setBottomSheetSystemBarsColor(sysUiController) + } ModalBottomSheetLayout( sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright, modifier = Modifier.background(Color.Transparent), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt index dfff3d694877..244b604a87f9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SnackBar.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalAccessibilityManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.android.compose.rememberSystemUiController import com.android.credentialmanager.R import com.android.credentialmanager.common.material.Scrim import com.android.credentialmanager.ui.theme.Shapes @@ -49,6 +50,8 @@ fun Snackbar( onDismiss: () -> Unit, dismissOnTimeout: Boolean = false, ) { + val sysUiController = rememberSystemUiController() + setTransparentSystemBarsColor(sysUiController) BoxWithConstraints { Box(Modifier.fillMaxSize()) { Scrim( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index ed4cc959543b..648d83268541 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap -import com.android.compose.rememberSystemUiController import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R import com.android.credentialmanager.common.BaseEntry @@ -67,7 +66,6 @@ import com.android.credentialmanager.common.ui.MoreOptionTopAppBar import com.android.credentialmanager.common.ui.SheetContainerCard import com.android.credentialmanager.common.ui.PasskeyBenefitRow import com.android.credentialmanager.common.ui.HeadlineText -import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor import com.android.credentialmanager.logging.CreateCredentialEvent import com.android.internal.logging.UiEventLogger.UiEventEnum @@ -77,8 +75,6 @@ fun CreateCredentialScreen( createCredentialUiState: CreateCredentialUiState, providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult> ) { - val sysUiController = rememberSystemUiController() - setBottomSheetSystemBarsColor(sysUiController) ModalBottomSheet( sheetContent = { // Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index c27ac943bca7..6d7ecd70eb0b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap -import com.android.compose.rememberSystemUiController import com.android.credentialmanager.CredentialSelectorViewModel import com.android.credentialmanager.R import com.android.credentialmanager.common.BaseEntry @@ -62,8 +61,6 @@ import com.android.credentialmanager.common.ui.CredentialListSectionHeader import com.android.credentialmanager.common.ui.HeadlineIcon import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.Snackbar -import com.android.credentialmanager.common.ui.setTransparentSystemBarsColor -import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor import com.android.credentialmanager.logging.GetCredentialEvent import com.android.internal.logging.UiEventLogger.UiEventEnum @@ -73,9 +70,7 @@ fun GetCredentialScreen( getCredentialUiState: GetCredentialUiState, providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult> ) { - val sysUiController = rememberSystemUiController() if (getCredentialUiState.currentScreenState == GetScreenState.REMOTE_ONLY) { - setTransparentSystemBarsColor(sysUiController) RemoteCredentialSnackBarScreen( onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected, onCancel = viewModel::onUserCancel, @@ -84,7 +79,6 @@ fun GetCredentialScreen( viewModel.uiMetrics.log(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY) } else if (getCredentialUiState.currentScreenState == GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY) { - setTransparentSystemBarsColor(sysUiController) EmptyAuthEntrySnackBarScreen( authenticationEntryList = getCredentialUiState.providerDisplayInfo.authenticationEntryList, @@ -95,7 +89,6 @@ fun GetCredentialScreen( viewModel.uiMetrics.log(GetCredentialEvent .CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY) } else { - setBottomSheetSystemBarsColor(sysUiController) ModalBottomSheet( sheetContent = { // Hide the sheet content as opposed to the whole bottom sheet to maintain the scrim diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7a97b78a4517..b0a19270018a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3748,7 +3748,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 216; + private static final int SETTINGS_VERSION = 217; private final int mUserId; @@ -5711,7 +5711,7 @@ public class SettingsProvider extends ContentProvider { .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE); if (currentSetting.isNull()) { final int resourceId = - com.android.internal.R.string.config_defaultCredentialProviderService; + com.android.internal.R.array.config_defaultCredentialProviderService; final Resources resources = getContext().getResources(); // If the config has not be defined we might get an exception. We also get // values from both the string array type and the single string in case the @@ -5771,6 +5771,39 @@ public class SettingsProvider extends ContentProvider { currentVersion = 216; } + if (currentVersion == 216) { + // Version 216: Set a default value for Credential Manager service. + // We are doing this migration again because of an incorrect setting. + + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final Setting currentSetting = secureSettings + .getSettingLocked(Settings.Secure.CREDENTIAL_SERVICE); + if (currentSetting.isNull()) { + final int resourceId = + com.android.internal.R.array.config_defaultCredentialProviderService; + final Resources resources = getContext().getResources(); + // If the config has not be defined we might get an exception. + final List<String> providers = new ArrayList<>(); + try { + providers.addAll(Arrays.asList(resources.getStringArray(resourceId))); + } catch (Resources.NotFoundException e) { + Slog.w(LOG_TAG, + "Get default array Cred Provider not found: " + e.toString()); + } + + if (!providers.isEmpty()) { + final String defaultValue = String.join(":", providers); + Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as CredMan Service " + + "for user " + userId); + secureSettings.insertSettingOverrideableByRestoreLocked( + Settings.Secure.CREDENTIAL_SERVICE, defaultValue, null, true, + SettingsState.SYSTEM_PACKAGE_NAME); + } + } + + currentVersion = 217; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java index df27f6a67cfa..bd99a8bbb09f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java @@ -170,6 +170,7 @@ final class WritableNamespacePrefixes { "widget", "wifi", "window_manager", - "window_manager_native_boot" + "window_manager_native_boot", + "wrong" )); } diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 067efe97f6b6..e069a9ac776b 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -455,7 +455,8 @@ public class BugreportProgressService extends Service { intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash); intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce); intent.putExtra(EXTRA_BUGREPORT, bugreportFileName); - context.sendBroadcast(intent, android.Manifest.permission.DUMP); + context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, + android.Manifest.permission.DUMP); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java index 6a6e81e9cb46..14810d9baf02 100644 --- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java @@ -30,7 +30,6 @@ import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.animation.Animation; import android.widget.ImageView; import android.widget.LinearLayout; @@ -50,7 +49,7 @@ public class PinShapeNonHintingView extends LinearLayout implements PinShapeInpu android.R.attr.textColorPrimary).getDefaultColor(); private int mPosition = 0; private final PinShapeAdapter mPinShapeAdapter; - private Animation mCurrentPlayingAnimation; + private ValueAnimator mValueAnimator = ValueAnimator.ofFloat(1f, 0f); public PinShapeNonHintingView(Context context, AttributeSet attrs) { super(context, attrs); mPinShapeAdapter = new PinShapeAdapter(context); @@ -80,15 +79,17 @@ public class PinShapeNonHintingView extends LinearLayout implements PinShapeInpu Log.e(getClass().getName(), "Trying to delete a non-existent char"); return; } + if (mValueAnimator.isRunning()) { + mValueAnimator.end(); + } mPosition--; ImageView pinDot = (ImageView) getChildAt(mPosition); - ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); - animator.addUpdateListener(valueAnimator -> { + mValueAnimator.addUpdateListener(valueAnimator -> { float value = (float) valueAnimator.getAnimatedValue(); pinDot.setScaleX(value); pinDot.setScaleY(value); }); - animator.addListener(new AnimatorListenerAdapter() { + mValueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); @@ -96,11 +97,10 @@ public class PinShapeNonHintingView extends LinearLayout implements PinShapeInpu PinShapeNonHintingView.this, new PinShapeViewTransition()); removeView(pinDot); - mCurrentPlayingAnimation = null; } }); - animator.setDuration(PasswordTextView.DISAPPEAR_DURATION); - animator.start(); + mValueAnimator.setDuration(PasswordTextView.DISAPPEAR_DURATION); + mValueAnimator.start(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 868ffcf54fe1..e0b9f01bf662 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -34,6 +34,7 @@ import com.android.systemui.animation.Interpolators import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal @@ -43,7 +44,6 @@ import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -52,7 +52,7 @@ import java.io.PrintWriter import javax.inject.Inject import javax.inject.Provider -/*** +/** * Controls two ripple effects: * 1. Unlocked ripple: shows when authentication is successful * 2. UDFPS dwell ripple: shows when the user has their finger down on the UDFPS area and reacts @@ -71,14 +71,15 @@ class AuthRippleController @Inject constructor( private val wakefulnessLifecycle: WakefulnessLifecycle, private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, - private val bypassController: KeyguardBypassController, - private val biometricUnlockController: BiometricUnlockController, private val udfpsControllerProvider: Provider<UdfpsController>, private val statusBarStateController: StatusBarStateController, private val featureFlags: FeatureFlags, private val logger: KeyguardLogger, - rippleView: AuthRippleView? -) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, + private val biometricUnlockController: BiometricUnlockController, + rippleView: AuthRippleView? +) : + ViewController<AuthRippleView>(rippleView), + KeyguardStateController.Callback, WakefulnessLifecycle.Observer { @VisibleForTesting @@ -102,8 +103,24 @@ class AuthRippleController @Inject constructor( keyguardStateController.addCallback(this) wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + biometricUnlockController.addListener(biometricModeListener) } + private val biometricModeListener = + object : BiometricUnlockController.BiometricUnlockEventsListener { + override fun onBiometricUnlockedWithKeyguardDismissal( + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType != null) { + showUnlockRipple(biometricSourceType) + } else { + logger.log(TAG, + LogLevel.ERROR, + "Unexpected scenario where biometricSourceType is null") + } + } + } + @VisibleForTesting public override fun onViewDetached() { udfpsController?.removeCallback(udfpsControllerCallback) @@ -113,6 +130,7 @@ class AuthRippleController @Inject constructor( keyguardStateController.removeCallback(this) wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") + biometricUnlockController.removeListener(biometricModeListener) notificationShadeWindowController.setForcePluginOpen(false, this) } @@ -143,9 +161,6 @@ class AuthRippleController @Inject constructor( showUnlockedRipple() } } else if (biometricSourceType == BiometricSourceType.FACE) { - if (!bypassController.canBypass() && !authController.isUdfpsFingerDown) { - return - } faceSensorLocation?.let { mView.setSensorLocation(it) circleReveal = CircleReveal( @@ -267,7 +282,6 @@ class AuthRippleController @Inject constructor( if (biometricSourceType == BiometricSourceType.FINGERPRINT) { mView.fadeDwellRipple() } - showUnlockRipple(biometricSourceType) } override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index 2ea7bce66452..570132e111eb 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -303,10 +303,6 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } flingToExpansion(verticalVelocity, expansion); - - if (expansion == KeyguardBouncerConstants.EXPANSION_HIDDEN) { - mCurrentScrimController.reset(); - } break; default: mVelocityTracker.addMovement(motionEvent); diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index a7b45f4faee3..0bc8506f272d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -285,7 +285,7 @@ object Flags { /** Enables Font Scaling Quick Settings tile */ // TODO(b/269341316): Tracking Bug @JvmField - val ENABLE_FONT_SCALING_TILE = unreleasedFlag(509, "enable_font_scaling_tile", teamfood = true) + val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile") /** Enables new QS Edit Mode visual refresh */ // TODO(b/269787742): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b1efdd733faa..c102c5b57375 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2856,14 +2856,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + " wasShowing=" + wasShowing); } + mKeyguardUnlockAnimationControllerLazy.get() + .notifyFinishedKeyguardExitAnimation(cancelled); finishSurfaceBehindRemoteAnimation(cancelled); // Dispatch the callback on animation finishes. mUpdateMonitor.dispatchKeyguardDismissAnimationFinished(); }); - mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation( - cancelled); } /** 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 6bc9837e8751..3567d814f63e 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 @@ -471,7 +471,7 @@ constructor( } val callback = - object : BiometricUnlockController.BiometricModeListener { + object : BiometricUnlockController.BiometricUnlockEventsListener { override fun onModeChanged(@WakeAndUnlockMode mode: Int) { dispatchUpdate() } @@ -481,10 +481,10 @@ constructor( } } - biometricUnlockController.addBiometricModeListener(callback) + biometricUnlockController.addListener(callback) dispatchUpdate() - awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } + awaitClose { biometricUnlockController.removeListener(callback) } } override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 8f1c9048026f..30ee147e302a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -63,6 +63,10 @@ constructor( override fun onStateChanged(newState: Int) { refreshMediaPosition() } + + override fun onDozingChanged(isDozing: Boolean) { + refreshMediaPosition() + } } ) configurationController.addCallback( @@ -198,7 +202,8 @@ constructor( mediaHost.visible && !bypassController.bypassEnabled && keyguardOrUserSwitcher && - allowMediaPlayerOnLockScreen + allowMediaPlayerOnLockScreen && + shouldBeVisibleForSplitShade() if (visible) { showMediaPlayer() } else { @@ -206,6 +211,19 @@ constructor( } } + private fun shouldBeVisibleForSplitShade(): Boolean { + if (!useSplitShade) { + return true + } + // We have to explicitly hide media for split shade when on AOD, as it is a child view of + // keyguard status view, and nothing hides keyguard status view on AOD. + // When using the double-line clock, it is not an issue, as media gets implicitly hidden + // by the clock. This is not the case for single-line clock though. + // For single shade, we don't need to do it, because media is a child of NSSL, which already + // gets hidden on AOD. + return !statusBarStateController.isDozing + } + private fun showMediaPlayer() { if (useSplitShade) { setVisibility(splitShadeContainer, View.VISIBLE) diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 9928c4f79a96..f50a7a854169 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -205,7 +205,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { && mController.isSubStatusSupported() && mController.isAdvancedLayoutSupported() && device.hasSubtext()) { boolean isActiveWithOngoingSession = - (device.hasOngoingSession() && currentlyConnected); + (device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded( + mController.getSelectedMediaDevice(), device))); boolean isHost = device.isHostForOngoingSession() && isActiveWithOngoingSession; if (isHost) { @@ -224,10 +225,17 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { if (isActiveWithOngoingSession) { //Selected device which has ongoing session, disable seekbar since we //only allow volume control on Host - initSeekbar(device, isCurrentSeekbarInvisible); mCurrentActivePosition = position; } - setUpDeviceIcon(device); + boolean showSeekbar = + (!device.hasOngoingSession() && currentlyConnected); + if (showSeekbar) { + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + initSeekbar(device, isCurrentSeekbarInvisible); + } else { + setUpDeviceIcon(device); + } mSubTitleText.setText(device.getSubtextString()); Drawable deviceStatusIcon = device.hasOngoingSession() ? mContext.getDrawable( @@ -241,8 +249,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateTwoLineLayoutContentAlpha( updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */, - isActiveWithOngoingSession /* showSeekBar */, + setTwoLineLayout(device, currentlyConnected /* bFocused */, + showSeekbar /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, deviceStatusIcon != null /* showStatus */, isActiveWithOngoingSession /* isFakeActive */); diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 731bb2f4db7c..73ab52722a79 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -270,10 +270,10 @@ public abstract class MediaOutputBaseAdapter extends final Drawable backgroundDrawable; if (mController.isAdvancedLayoutSupported() && mController.isSubStatusSupported()) { backgroundDrawable = mContext.getDrawable( - showSeekBar ? R.drawable.media_output_item_background_active + showSeekBar || isFakeActive ? R.drawable.media_output_item_background_active : R.drawable.media_output_item_background).mutate(); backgroundDrawable.setTint( - showSeekBar ? mController.getColorConnectedItemBackground() + showSeekBar || isFakeActive ? mController.getColorConnectedItemBackground() : mController.getColorItemBackground()); mIconAreaLayout.setBackgroundTintList( ColorStateList.valueOf(showProgressBar || isFakeActive diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt index 70040c75d123..c804df8fa555 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt @@ -363,12 +363,8 @@ class BackPanel( } fun popOffEdge(startingVelocity: Float) { - val heightStretchAmount = startingVelocity * 50 - val widthStretchAmount = startingVelocity * 150 - val scaleStretchAmount = startingVelocity * 0.8f - backgroundHeight.stretchTo(stretchAmount = 0f, startingVelocity = -heightStretchAmount) - backgroundWidth.stretchTo(stretchAmount = 0f, startingVelocity = widthStretchAmount) - scale.stretchTo(stretchAmount = 0f, startingVelocity = -scaleStretchAmount) + scale.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity * -.8f) + horizontalTranslation.stretchTo(stretchAmount = 0f, startingVelocity * 200f) } fun popScale(startingVelocity: Float) { @@ -410,7 +406,7 @@ class BackPanel( arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate) arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate) arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate) - scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate) + scalePivotX.updateRestingPosition(restingParams.scalePivotX, animate) backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate) backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate) backgroundEdgeCornerRadius.updateRestingPosition( diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index a29eb3bda748..3770b2885b18 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -49,6 +49,7 @@ import kotlin.math.sign private const val TAG = "BackPanelController" private const val ENABLE_FAILSAFE = true +private const val FAILSAFE_DELAY_MS = 350L private const val PX_PER_SEC = 1000 private const val PX_PER_MS = 1 @@ -64,9 +65,9 @@ private const val MIN_DURATION_FLING_ANIMATION = 160L private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L -private const val FAILSAFE_DELAY_MS = 350L -private const val POP_ON_FLING_DELAY = 50L -private const val POP_ON_FLING_SCALE = 3f +private const val POP_ON_FLING_DELAY = 60L +private const val POP_ON_FLING_SCALE = 2f +private const val POP_ON_COMMITTED_SCALE = 3f internal val VIBRATE_ACTIVATED_EFFECT = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) @@ -774,7 +775,9 @@ class BackPanelController internal constructor( GestureState.ENTRY, GestureState.INACTIVE, GestureState.CANCELLED -> params.preThresholdIndicator.scalePivotX - else -> params.committedIndicator.scalePivotX + GestureState.ACTIVE -> params.activeIndicator.scalePivotX + GestureState.FLUNG, + GestureState.COMMITTED -> params.committedIndicator.scalePivotX }, horizontalTranslation = when (currentState) { GestureState.GONE -> { @@ -921,7 +924,7 @@ class BackPanelController internal constructor( mainHandler.postDelayed(onEndSetGoneStateListener.runnable, MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION) } else { - mView.popScale(POP_ON_FLING_SCALE) + mView.popScale(POP_ON_COMMITTED_SCALE) mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable, MIN_DURATION_COMMITTED_ANIMATION) } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index 6ce6f0d5f722..c9d8c8495dcc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -35,7 +35,7 @@ data class EdgePanelParams(private var resources: Resources) { data class BackIndicatorDimens( val horizontalTranslation: Float? = 0f, val scale: Float = 0f, - val scalePivotX: Float = 0f, + val scalePivotX: Float? = null, val arrowDimens: ArrowDimens, val backgroundDimens: BackgroundDimens, val verticalTranslationSpring: SpringForce? = null, @@ -203,7 +203,8 @@ data class EdgePanelParams(private var resources: Resources) { horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), horizontalTranslationSpring = entryActiveHorizontalTranslationSpring, - scaleSpring = createSpring(450f, 0.415f), + scaleSpring = createSpring(450f, 0.39f), + scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width), arrowDimens = ArrowDimens( length = getDimen(R.dimen.navigation_edge_active_arrow_length), height = getDimen(R.dimen.navigation_edge_active_arrow_height), @@ -258,6 +259,7 @@ data class EdgePanelParams(private var resources: Resources) { committedIndicator = activeIndicator.copy( horizontalTranslation = null, + scalePivotX = null, arrowDimens = activeIndicator.arrowDimens.copy( lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index f7790e861e27..789873675bfa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -22,7 +22,6 @@ import static android.app.Notification.CATEGORY_EVENT; import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.Notification.CATEGORY_REMINDER; import static android.app.Notification.FLAG_BUBBLE; -import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT; @@ -825,8 +824,7 @@ public final class NotificationEntry extends ListEntry { return false; } - if ((mSbn.getNotification().flags - & FLAG_FOREGROUND_SERVICE) != 0) { + if (mSbn.getNotification().isFgsOrUij()) { return true; } if (mSbn.getNotification().isMediaNotification()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index ca762fc1ddc2..a48870ba9f45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -630,6 +630,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter return false; } + if (notification.isUserInitiatedJob()) { + if (log) mLogger.logMaybeHeadsUpDespiteOldWhen(entry, when, age, "user initiated job"); + return false; + } + if (log) mLogger.logNoHeadsUpOldWhen(entry, when, age); final int uid = entry.getSbn().getUid(); final String packageName = entry.getSbn().getPackageName(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index cf5ecdddf854..6742e4f3041e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -163,7 +163,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private PendingAuthenticated mPendingAuthenticated = null; private boolean mHasScreenTurnedOnSinceAuthenticating; private boolean mFadedAwayAfterWakeAndUnlock; - private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>(); + private Set<BiometricUnlockEventsListener> mBiometricUnlockEventsListeners = new HashSet<>(); private final MetricsLogger mMetricsLogger; private final AuthController mAuthController; @@ -314,14 +314,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewController = keyguardViewController; } - /** Adds a {@link BiometricModeListener}. */ - public void addBiometricModeListener(BiometricModeListener listener) { - mBiometricModeListeners.add(listener); + /** Adds a {@link BiometricUnlockEventsListener}. */ + public void addListener(BiometricUnlockEventsListener listener) { + mBiometricUnlockEventsListeners.add(listener); } - /** Removes a {@link BiometricModeListener}. */ - public void removeBiometricModeListener(BiometricModeListener listener) { - mBiometricModeListeners.remove(listener); + /** Removes a {@link BiometricUnlockEventsListener}. */ + public void removeListener(BiometricUnlockEventsListener listener) { + mBiometricUnlockEventsListeners.remove(listener); } private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() { @@ -387,7 +387,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp @Override public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric) { - Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated"); + Trace.beginSection("BiometricUnlockController#onBiometricUnlocked"); if (mUpdateMonitor.isGoingToSleep()) { mLogger.deferringAuthenticationDueToSleep(userId, biometricSourceType, @@ -411,10 +411,15 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mKeyguardViewMediator.userActivity(); startWakeAndUnlock(biometricSourceType, isStrongBiometric); } else { - mLogger.d("onBiometricAuthenticated aborted by bypass controller"); + mLogger.d("onBiometricUnlocked aborted by bypass controller"); } } + /** + * Wake and unlock the device in response to successful authentication using biometrics. + * @param biometricSourceType Biometric source that was used to authenticate. + * @param isStrongBiometric + */ public void startWakeAndUnlock(BiometricSourceType biometricSourceType, boolean isStrongBiometric) { int mode = calculateMode(biometricSourceType, isStrongBiometric); @@ -422,6 +427,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { vibrateSuccess(biometricSourceType); + onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); } @@ -502,11 +508,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } private void onModeChanged(@WakeAndUnlockMode int mode) { - for (BiometricModeListener listener : mBiometricModeListeners) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onModeChanged(mode); } } + private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { + listener.onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); + } + } + public boolean hasPendingAuthentication() { return mPendingAuthenticated != null && mUpdateMonitor @@ -777,7 +789,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mMode = MODE_NONE; mBiometricType = null; mNotificationShadeWindowController.setForceDozeBrightness(false); - for (BiometricModeListener listener : mBiometricModeListeners) { + for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onResetMode(); } mNumConsecutiveFpFailures = 0; @@ -895,10 +907,17 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } /** An interface to interact with the {@link BiometricUnlockController}. */ - public interface BiometricModeListener { + public interface BiometricUnlockEventsListener { /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */ default void onResetMode() {} /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */ default void onModeChanged(@WakeAndUnlockMode int mode) {} + + /** + * Called when the device is unlocked successfully using biometrics with the keyguard also + * being dismissed. + */ + default void onBiometricUnlockedWithKeyguardDismissal( + BiometricSourceType biometricSourceType) { } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index aabe0cb9794c..0c8e9e56b04a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1675,8 +1675,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarStateController.addCallback(mStateListener, SysuiStatusBarStateController.RANK_STATUS_BAR); mBiometricUnlockController = mBiometricUnlockControllerLazy.get(); - mBiometricUnlockController.addBiometricModeListener( - new BiometricUnlockController.BiometricModeListener() { + mBiometricUnlockController.addListener( + new BiometricUnlockController.BiometricUnlockEventsListener() { @Override public void onResetMode() { setWakeAndUnlocking(false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 30d2295206d8..a8a834f1e8f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -558,8 +558,10 @@ public interface StatusBarIconController { mGroup.addView(view, index, onCreateLayoutParams()); if (mIsInDemoMode) { + Context mobileContext = mMobileContextProvider + .getMobileContextForSub(subId, mContext); mDemoStatusIcons.addModernMobileView( - mContext, + mobileContext, mMobileIconsViewModel.getLogger(), subId); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index edfc95fcc2e7..c623201b2a6b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -618,9 +618,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte if ((flags & Notification.FLAG_AUTO_CANCEL) != Notification.FLAG_AUTO_CANCEL) { return false; } - if ((flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - return false; - } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 90c32dc08045..3a11635f75c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -40,6 +40,9 @@ interface MobileConnectionRepository { /** The subscriptionId that this connection represents */ val subId: Int + /** The carrierId for this connection. See [TelephonyManager.getSimCarrierId] */ + val carrierId: StateFlow<Int> + /** * The table log buffer created for this connection. Will have the name "MobileConnectionLog * [subId]" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt index 809772eec2f0..6b86432b8171 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo import android.telephony.CellSignalStrength +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyManager import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable @@ -25,6 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameMode import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel +import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_ID import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY @@ -52,6 +54,17 @@ class DemoMobileConnectionRepository( override val tableLogBuffer: TableLogBuffer, val scope: CoroutineScope, ) : MobileConnectionRepository { + private val _carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) + override val carrierId = + _carrierId + .logDiffsForTable( + tableLogBuffer, + columnPrefix = "", + columnName = COL_CARRIER_ID, + _carrierId.value, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierId.value) + private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly @@ -186,6 +199,8 @@ class DemoMobileConnectionRepository( dataEnabled.value = true networkName.value = NetworkNameModel.IntentDerived(event.name) + _carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID + cdmaRoaming.value = event.roaming _isRoaming.value = event.roaming // TODO(b/261029387): not yet supported @@ -208,6 +223,8 @@ class DemoMobileConnectionRepository( // This is always true here, because we split out disabled states at the data-source level dataEnabled.value = true networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME) + // TODO(b/276943904): is carrierId a thing with carrier merged networks? + _carrierId.value = INVALID_SUBSCRIPTION_ID numberOfLevels.value = event.numberOfLevels cdmaRoaming.value = false _primaryLevel.value = event.level diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt index 94d6d0b1db44..a609917351d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyManager import android.util.Log import com.android.systemui.dagger.SysUISingleton @@ -157,6 +158,7 @@ class CarrierMergedConnectionRepository( .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected) override val isRoaming = MutableStateFlow(false).asStateFlow() + override val carrierId = MutableStateFlow(INVALID_SUBSCRIPTION_ID).asStateFlow() override val isEmergencyOnly = MutableStateFlow(false).asStateFlow() override val operatorAlphaShort = MutableStateFlow(null).asStateFlow() override val isInService = MutableStateFlow(true).asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt index b3737ecd1e0b..8869dfe02697 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt @@ -109,6 +109,11 @@ class FullMobileConnectionRepository( .stateIn(scope, SharingStarted.WhileSubscribed(), initial) } + override val carrierId = + activeRepo + .flatMapLatest { it.carrierId } + .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.carrierId.value) + override val cdmaRoaming = activeRepo .flatMapLatest { it.cdmaRoaming } @@ -321,13 +326,14 @@ class FullMobileConnectionRepository( } companion object { + const val COL_CARRIER_ID = "carrierId" + const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive" + const val COL_CDMA_LEVEL = "cdmaLevel" const val COL_EMERGENCY = "emergencyOnly" - const val COL_ROAMING = "roaming" - const val COL_OPERATOR = "operatorName" - const val COL_IS_IN_SERVICE = "isInService" const val COL_IS_GSM = "isGsm" - const val COL_CDMA_LEVEL = "cdmaLevel" + const val COL_IS_IN_SERVICE = "isInService" + const val COL_OPERATOR = "operatorName" const val COL_PRIMARY_LEVEL = "primaryLevel" - const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive" + const val COL_ROAMING = "roaming" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt index d0c6215a55d8..b475183d98c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod -import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN import android.telephony.CellSignalStrengthCdma @@ -31,6 +31,7 @@ import android.telephony.TelephonyManager.ERI_FLASH import android.telephony.TelephonyManager.ERI_ON import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN +import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import com.android.settingslib.Utils import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Application @@ -65,6 +66,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.stateIn @@ -75,7 +77,6 @@ import kotlinx.coroutines.flow.stateIn @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( - private val context: Context, override val subId: Int, defaultNetworkName: NetworkNameModel, networkNameSeparator: String, @@ -293,6 +294,23 @@ class MobileConnectionRepositoryImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val carrierId = + broadcastDispatcher + .broadcastFlow( + filter = + IntentFilter(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED), + map = { intent, _ -> intent }, + ) + .filter { intent -> + intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) == subId + } + .map { it.carrierId() } + .onStart { + // Make sure we get the initial carrierId + emit(telephonyManager.simCarrierId) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), telephonyManager.simCarrierId) + override val networkName: StateFlow<NetworkNameModel> = broadcastDispatcher .broadcastFlow( @@ -317,7 +335,6 @@ class MobileConnectionRepositoryImpl( @Inject constructor( private val broadcastDispatcher: BroadcastDispatcher, - private val context: Context, private val telephonyManager: TelephonyManager, private val logger: MobileInputLogger, private val carrierConfigRepository: CarrierConfigRepository, @@ -332,7 +349,6 @@ class MobileConnectionRepositoryImpl( networkNameSeparator: String, ): MobileConnectionRepository { return MobileConnectionRepositoryImpl( - context, subId, defaultNetworkName, networkNameSeparator, @@ -349,6 +365,9 @@ class MobileConnectionRepositoryImpl( } } +private fun Intent.carrierId(): Int = + getIntExtra(TelephonyManager.EXTRA_CARRIER_ID, UNKNOWN_CARRIER_ID) + /** * Wrap every [TelephonyCallback] we care about in a data class so we can accept them in a single * shared flow and then split them back out into other flows. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index 22351f8b2821..b36ba3845fe9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -16,15 +16,21 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor +import android.content.Context import android.telephony.CarrierConfigManager import com.android.settingslib.SignalIcon.MobileIconGroup -import com.android.settingslib.mobile.TelephonyIcons.NOT_DEFAULT_DATA +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides +import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,8 +40,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn interface MobileIconInteractor { @@ -76,7 +80,7 @@ interface MobileIconInteractor { val alwaysUseCdmaLevel: StateFlow<Boolean> /** Observable for RAT type (network type) indicator */ - val networkTypeIconGroup: StateFlow<MobileIconGroup> + val networkTypeIconGroup: StateFlow<NetworkTypeIconModel> /** * Provider name for this network connection. The name can be one of 3 values: @@ -119,10 +123,11 @@ class MobileIconInteractorImpl( override val mobileIsDefault: StateFlow<Boolean>, defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, defaultMobileIconGroup: StateFlow<MobileIconGroup>, - defaultDataSubId: StateFlow<Int>, override val isDefaultConnectionFailed: StateFlow<Boolean>, override val isForceHidden: Flow<Boolean>, connectionRepository: MobileConnectionRepository, + private val context: Context, + val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) : MobileIconInteractor { override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer @@ -130,14 +135,14 @@ class MobileIconInteractorImpl( override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled - private val isDefault = - defaultDataSubId - .mapLatest { connectionRepository.subId == it } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - connectionRepository.subId == defaultDataSubId.value - ) + // True if there exists _any_ icon override for this carrierId. Note that overrides can include + // any or none of the icon groups defined in MobileMappings, so we still need to check on a + // per-network-type basis whether or not the given icon group is overridden + private val carrierIdIconOverrideExists = + connectionRepository.carrierId + .map { carrierIdOverrides.carrierIdEntryExists(it) } + .distinctUntilChanged() + .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled @@ -157,36 +162,57 @@ class MobileIconInteractorImpl( connectionRepository.networkName.value ) - /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ - override val networkTypeIconGroup: StateFlow<MobileIconGroup> = + /** What the mobile icon would be before carrierId overrides */ + private val defaultNetworkType: StateFlow<MobileIconGroup> = combine( connectionRepository.resolvedNetworkType, defaultMobileIconMapping, defaultMobileIconGroup, - isDefault, - ) { resolvedNetworkType, mapping, defaultGroup, isDefault -> - if (!isDefault) { - return@combine NOT_DEFAULT_DATA - } - + ) { resolvedNetworkType, mapping, defaultGroup -> when (resolvedNetworkType) { is ResolvedNetworkType.CarrierMergedNetworkType -> resolvedNetworkType.iconGroupOverride - else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup + else -> { + mapping[resolvedNetworkType.lookupKey] ?: defaultGroup + } } } - .distinctUntilChanged() - .onEach { - // Doesn't use [logDiffsForTable] because [MobileIconGroup] can't implement the - // [Diffable] interface. - tableLogBuffer.logChange( - prefix = "", - columnName = "networkTypeIcon", - value = it.name - ) - } .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) + override val networkTypeIconGroup = + combine( + defaultNetworkType, + carrierIdIconOverrideExists, + ) { networkType, overrideExists -> + // DefaultIcon comes out of the icongroup lookup, we check for overrides here + if (overrideExists) { + val iconOverride = + carrierIdOverrides.getOverrideFor( + connectionRepository.carrierId.value, + networkType.name, + context.resources, + ) + if (iconOverride > 0) { + OverriddenIcon(networkType, iconOverride) + } else { + DefaultIcon(networkType) + } + } else { + DefaultIcon(networkType) + } + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnPrefix = "", + initialValue = DefaultIcon(defaultMobileIconGroup.value), + ) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + DefaultIcon(defaultMobileIconGroup.value), + ) + override val isEmergencyOnly = connectionRepository.isEmergencyOnly override val isRoaming: StateFlow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index 6c8310ac3d29..1e3122b1a515 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor +import android.content.Context import android.telephony.CarrierConfigManager import android.telephony.SubscriptionManager import com.android.settingslib.SignalIcon.MobileIconGroup @@ -75,9 +76,6 @@ interface MobileIconsInteractor { /** True if the CDMA level should be preferred over the primary level. */ val alwaysUseCdmaLevel: StateFlow<Boolean> - /** Tracks the subscriptionId set as the default for data connections */ - val defaultDataSubId: StateFlow<Int> - /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> @@ -112,6 +110,7 @@ constructor( connectivityRepository: ConnectivityRepository, userSetupRepo: UserSetupRepository, @Application private val scope: CoroutineScope, + private val context: Context, ) : MobileIconsInteractor { override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault @@ -184,8 +183,6 @@ constructor( ) .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) - override val defaultDataSubId = mobileConnectionsRepo.defaultDataSubId - /** * Copied from the old pipeline. We maintain a 2s period of time where we will keep the * validated bit from the old active network (A) while data is changing to the new one (B). @@ -282,10 +279,10 @@ constructor( mobileIsDefault, defaultMobileIconMapping, defaultMobileIconGroup, - defaultDataSubId, isDefaultConnectionFailed, isForceHidden, mobileConnectionsRepo.getRepoForSubId(subId), + context, ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt new file mode 100644 index 000000000000..6ea5f90ddc71 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/NetworkTypeIconModel.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.mobile.domain.model + +import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger + +/** + * A data wrapper class for [MobileIconGroup]. One lingering nuance of this pipeline is its + * dependency on MobileMappings for its lookup from NetworkType -> NetworkTypeIcon. And because + * MobileMappings is a static map of (netType, icon) that knows nothing of `carrierId`, we need the + * concept of a "default" or "overridden" icon type. + * + * Until we can remove that dependency on MobileMappings, we should just allow for the composition + * of overriding an icon id using the lookup defined in [MobileIconCarrierIdOverrides]. By using the + * [overrideIcon] method defined below, we can create any arbitrarily overridden network type icon. + */ +sealed interface NetworkTypeIconModel : Diffable<NetworkTypeIconModel> { + val contentDescription: Int + val iconId: Int + val name: String + + data class DefaultIcon( + val iconGroup: MobileIconGroup, + ) : NetworkTypeIconModel { + override val contentDescription = iconGroup.dataContentDescription + override val iconId = iconGroup.dataType + override val name = iconGroup.name + + override fun logDiffs(prevVal: NetworkTypeIconModel, row: TableRowLogger) { + if (prevVal !is DefaultIcon || prevVal.name != name) { + row.logChange(COL_NETWORK_ICON, name) + } + } + } + + data class OverriddenIcon( + val iconGroup: MobileIconGroup, + override val iconId: Int, + ) : NetworkTypeIconModel { + override val contentDescription = iconGroup.dataContentDescription + override val name = iconGroup.name + + override fun logDiffs(prevVal: NetworkTypeIconModel, row: TableRowLogger) { + if (prevVal !is OverriddenIcon || prevVal.name != name || prevVal.iconId != iconId) { + row.logChange(COL_NETWORK_ICON, "Ovrd($name)") + } + } + } + + companion object { + const val COL_NETWORK_ICON = "networkTypeIcon" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 0fd007cf40ef..dce7bf21fadd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -146,11 +146,10 @@ constructor( combine( iconInteractor.isDataConnected, iconInteractor.isDataEnabled, - iconInteractor.isDefaultConnectionFailed, iconInteractor.alwaysShowDataRatIcon, iconInteractor.mobileIsDefault, - ) { dataConnected, dataEnabled, failedConnection, alwaysShow, mobileIsDefault -> - alwaysShow || (dataConnected && dataEnabled && !failedConnection && mobileIsDefault) + ) { dataConnected, dataEnabled, alwaysShow, mobileIsDefault -> + alwaysShow || (dataEnabled && dataConnected && mobileIsDefault) } .distinctUntilChanged() .logDiffsForTable( @@ -167,10 +166,13 @@ constructor( showNetworkTypeIcon, ) { networkTypeIconGroup, shouldShow -> val desc = - if (networkTypeIconGroup.dataContentDescription != 0) - ContentDescription.Resource(networkTypeIconGroup.dataContentDescription) + if (networkTypeIconGroup.contentDescription != 0) + ContentDescription.Resource(networkTypeIconGroup.contentDescription) + else null + val icon = + if (networkTypeIconGroup.iconId != 0) + Icon.Resource(networkTypeIconGroup.iconId, desc) else null - val icon = Icon.Resource(networkTypeIconGroup.dataType, desc) return@combine when { !shouldShow -> null else -> icon diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index a245c01d74de..7d9ccb642e47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.leak.RotationUtils @@ -50,6 +49,7 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.eq +import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.never @@ -73,16 +73,28 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var authController: AuthController @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock private lateinit var bypassController: KeyguardBypassController - @Mock private lateinit var biometricUnlockController: BiometricUnlockController - @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> - @Mock private lateinit var udfpsController: UdfpsController - @Mock private lateinit var statusBarStateController: StatusBarStateController - @Mock private lateinit var featureFlags: FeatureFlags - @Mock private lateinit var lightRevealScrim: LightRevealScrim - @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + @Mock + private lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock + private lateinit var notificationShadeWindowController: NotificationShadeWindowController + @Mock + private lateinit var biometricUnlockController: BiometricUnlockController + @Mock + private lateinit var udfpsControllerProvider: Provider<UdfpsController> + @Mock + private lateinit var udfpsController: UdfpsController + @Mock + private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var lightRevealScrim: LightRevealScrim + @Mock + private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal + + @Captor + private lateinit var biometricUnlockListener: + ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener> @Before fun setUp() { @@ -106,13 +118,12 @@ class AuthRippleControllerTest : SysuiTestCase() { wakefulnessLifecycle, commandRegistry, notificationShadeWindowController, - bypassController, - biometricUnlockController, udfpsControllerProvider, statusBarStateController, featureFlags, KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - rippleView + biometricUnlockController, + rippleView, ) controller.init() `when`(mCentralSurfaces.lightRevealScrim).thenReturn(lightRevealScrim) @@ -134,12 +145,9 @@ class AuthRippleControllerTest : SysuiTestCase() { eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) // WHEN fingerprint authenticated - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) + verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) + biometricUnlockListener.value + .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) // THEN update sensor location and show ripple verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) @@ -191,51 +199,6 @@ class AuthRippleControllerTest : SysuiTestCase() { } @Test - fun testFaceTriggerBypassEnabled_Ripple() { - // GIVEN face auth sensor exists, keyguard is showing & unlocking with face is allowed - val faceLocation = Point(5, 5) - `when`(authController.faceSensorLocation).thenReturn(faceLocation) - controller.onViewAttached() - - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FACE)).thenReturn(true) - - // WHEN bypass is enabled & face authenticated - `when`(bypassController.canBypass()).thenReturn(true) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - - // THEN show ripple - verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startUnlockedRipple(any()) - } - - @Test - fun testFaceTriggerNonBypass_NoRipple() { - // GIVEN face auth sensor exists - val faceLocation = Point(5, 5) - `when`(authController.faceSensorLocation).thenReturn(faceLocation) - controller.onViewAttached() - - // WHEN bypass isn't enabled & face authenticated - `when`(bypassController.canBypass()).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test fun testNullFaceSensorLocationDoesNothing() { `when`(authController.faceSensorLocation).thenReturn(null) controller.onViewAttached() diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt index 68846168d17b..f4cc8bcb09a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt @@ -60,6 +60,12 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { } val TEST_STRUCTURE: CharSequence = "TestStructure" val TEST_APP: CharSequence = "TestApp" + + private fun View.waitForPost() { + val latch = CountDownLatch(1) + post(latch::countDown) + latch.await() + } } @Main private val executor: Executor = MoreExecutors.directExecutor() @@ -140,7 +146,10 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { val rearrangeButton = requireViewById<Button>(R.id.rearrange) assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE) assertThat(rearrangeButton.isEnabled).isFalse() - assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.GONE) + + val otherAppsButton = requireViewById<Button>(R.id.other_apps) + otherAppsButton.waitForPost() + assertThat(otherAppsButton.visibility).isEqualTo(View.GONE) } } @@ -160,7 +169,10 @@ class ControlsFavoritingActivityTest : SysuiTestCase() { val rearrangeButton = requireViewById<Button>(R.id.rearrange) assertThat(rearrangeButton.visibility).isEqualTo(View.GONE) assertThat(rearrangeButton.isEnabled).isFalse() - assertThat(requireViewById<Button>(R.id.other_apps).visibility).isEqualTo(View.VISIBLE) + + val otherAppsButton = requireViewById<Button>(R.id.other_apps) + otherAppsButton.waitForPost() + assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE) } } 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 ce17167f587d..3fd97da23157 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 @@ -461,8 +461,8 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this) runCurrent() - val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>() - verify(biometricUnlockController).addBiometricModeListener(captor.capture()) + val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>() + verify(biometricUnlockController).addListener(captor.capture()) listOf( BiometricUnlockController.MODE_NONE, @@ -498,7 +498,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() runCurrent() - verify(biometricUnlockController).removeBiometricModeListener(captor.value) + verify(biometricUnlockController).removeListener(captor.value) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index 20260069c943..b40ebc9bb156 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -24,12 +24,14 @@ import android.view.View.GONE import android.view.View.VISIBLE import android.widget.FrameLayout import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.stack.MediaContainerView import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat @@ -39,8 +41,9 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit @SmallTest @@ -61,9 +64,16 @@ class KeyguardMediaControllerTest : SysuiTestCase() { private lateinit var keyguardMediaController: KeyguardMediaController private lateinit var testableLooper: TestableLooper private lateinit var fakeHandler: FakeHandler + private lateinit var statusBarStateListener: StatusBarStateController.StateListener @Before fun setup() { + doAnswer { + statusBarStateListener = it.arguments[0] as StatusBarStateController.StateListener + return@doAnswer Unit + } + .whenever(statusBarStateController) + .addCallback(any(StatusBarStateController.StateListener::class.java)) // default state is positive, media should show up whenever(mediaHost.visible).thenReturn(true) whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) @@ -170,4 +180,31 @@ class KeyguardMediaControllerTest : SysuiTestCase() { fun testMediaHost_expandedPlayer() { verify(mediaHost).expansion = MediaHostState.EXPANDED } + + @Test + fun dozing_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + setDozing() + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test + fun dozing_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + setDozing() + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + + private fun setDozing() { + whenever(statusBarStateController.isDozing).thenReturn(true) + statusBarStateListener.onDozingChanged(true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 17d8799b4f84..7f7952feb10b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -532,7 +532,7 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 4bb14a1eba0d..ba91d87c659a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -71,241 +71,241 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper class HeadsUpCoordinatorTest : SysuiTestCase() { - private lateinit var mCoordinator: HeadsUpCoordinator + private lateinit var coordinator: HeadsUpCoordinator // captured listeners and pluggables: - private lateinit var mCollectionListener: NotifCollectionListener - private lateinit var mNotifPromoter: NotifPromoter - private lateinit var mNotifLifetimeExtender: NotifLifetimeExtender - private lateinit var mBeforeTransformGroupsListener: OnBeforeTransformGroupsListener - private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener - private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener - private lateinit var mNotifSectioner: NotifSectioner - private lateinit var mActionPressListener: Consumer<NotificationEntry> - - private val mNotifPipeline: NotifPipeline = mock() - private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true) - private val mHeadsUpManager: HeadsUpManager = mock() - private val mHeadsUpViewBinder: HeadsUpViewBinder = mock() - private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider = mock() - private val mRemoteInputManager: NotificationRemoteInputManager = mock() - private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock() - private val mHeaderController: NodeController = mock() - private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock() - private val mFlags: NotifPipelineFlags = mock() - - private lateinit var mEntry: NotificationEntry - private lateinit var mGroupSummary: NotificationEntry - private lateinit var mGroupPriority: NotificationEntry - private lateinit var mGroupSibling1: NotificationEntry - private lateinit var mGroupSibling2: NotificationEntry - private lateinit var mGroupChild1: NotificationEntry - private lateinit var mGroupChild2: NotificationEntry - private lateinit var mGroupChild3: NotificationEntry - private val mSystemClock = FakeSystemClock() - private val mExecutor = FakeExecutor(mSystemClock) - private val mHuns: ArrayList<NotificationEntry> = ArrayList() - private lateinit var mHelper: NotificationGroupTestHelper + private lateinit var collectionListener: NotifCollectionListener + private lateinit var notifPromoter: NotifPromoter + private lateinit var notifLifetimeExtender: NotifLifetimeExtender + private lateinit var beforeTransformGroupsListener: OnBeforeTransformGroupsListener + private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener + private lateinit var onHeadsUpChangedListener: OnHeadsUpChangedListener + private lateinit var notifSectioner: NotifSectioner + private lateinit var actionPressListener: Consumer<NotificationEntry> + + private val notifPipeline: NotifPipeline = mock() + private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true) + private val headsUpManager: HeadsUpManager = mock() + private val headsUpViewBinder: HeadsUpViewBinder = mock() + private val notificationInterruptStateProvider: NotificationInterruptStateProvider = mock() + private val remoteInputManager: NotificationRemoteInputManager = mock() + private val endLifetimeExtension: OnEndLifetimeExtensionCallback = mock() + private val headerController: NodeController = mock() + private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock() + private val flags: NotifPipelineFlags = mock() + + private lateinit var entry: NotificationEntry + private lateinit var groupSummary: NotificationEntry + private lateinit var groupPriority: NotificationEntry + private lateinit var groupSibling1: NotificationEntry + private lateinit var groupSibling2: NotificationEntry + private lateinit var groupChild1: NotificationEntry + private lateinit var groupChild2: NotificationEntry + private lateinit var groupChild3: NotificationEntry + private val systemClock = FakeSystemClock() + private val executor = FakeExecutor(systemClock) + private val huns: ArrayList<NotificationEntry> = ArrayList() + private lateinit var helper: NotificationGroupTestHelper @Before fun setUp() { MockitoAnnotations.initMocks(this) - mHelper = NotificationGroupTestHelper(mContext) - mCoordinator = HeadsUpCoordinator( - mLogger, - mSystemClock, - mHeadsUpManager, - mHeadsUpViewBinder, - mNotificationInterruptStateProvider, - mRemoteInputManager, - mLaunchFullScreenIntentProvider, - mFlags, - mHeaderController, - mExecutor) - mCoordinator.attach(mNotifPipeline) + helper = NotificationGroupTestHelper(mContext) + coordinator = HeadsUpCoordinator( + logger, + systemClock, + headsUpManager, + headsUpViewBinder, + notificationInterruptStateProvider, + remoteInputManager, + launchFullScreenIntentProvider, + flags, + headerController, + executor) + coordinator.attach(notifPipeline) // capture arguments: - mCollectionListener = withArgCaptor { - verify(mNotifPipeline).addCollectionListener(capture()) + collectionListener = withArgCaptor { + verify(notifPipeline).addCollectionListener(capture()) } - mNotifPromoter = withArgCaptor { - verify(mNotifPipeline).addPromoter(capture()) + notifPromoter = withArgCaptor { + verify(notifPipeline).addPromoter(capture()) } - mNotifLifetimeExtender = withArgCaptor { - verify(mNotifPipeline).addNotificationLifetimeExtender(capture()) + notifLifetimeExtender = withArgCaptor { + verify(notifPipeline).addNotificationLifetimeExtender(capture()) } - mBeforeTransformGroupsListener = withArgCaptor { - verify(mNotifPipeline).addOnBeforeTransformGroupsListener(capture()) + beforeTransformGroupsListener = withArgCaptor { + verify(notifPipeline).addOnBeforeTransformGroupsListener(capture()) } - mBeforeFinalizeFilterListener = withArgCaptor { - verify(mNotifPipeline).addOnBeforeFinalizeFilterListener(capture()) + beforeFinalizeFilterListener = withArgCaptor { + verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture()) } - mOnHeadsUpChangedListener = withArgCaptor { - verify(mHeadsUpManager).addListener(capture()) + onHeadsUpChangedListener = withArgCaptor { + verify(headsUpManager).addListener(capture()) } - mActionPressListener = withArgCaptor { - verify(mRemoteInputManager).addActionPressListener(capture()) + actionPressListener = withArgCaptor { + verify(remoteInputManager).addActionPressListener(capture()) } - given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() } - given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation -> + given(headsUpManager.allEntries).willAnswer { huns.stream() } + given(headsUpManager.isAlerting(anyString())).willAnswer { invocation -> val key = invocation.getArgument<String>(0) - mHuns.any { entry -> entry.key == key } + huns.any { entry -> entry.key == key } } - given(mHeadsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation -> + given(headsUpManager.canRemoveImmediately(anyString())).willAnswer { invocation -> val key = invocation.getArgument<String>(0) - !mHuns.any { entry -> entry.key == key } + !huns.any { entry -> entry.key == key } } - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - mNotifSectioner = mCoordinator.sectioner - mNotifLifetimeExtender.setCallback(mEndLifetimeExtension) - mEntry = NotificationEntryBuilder().build() + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + notifSectioner = coordinator.sectioner + notifLifetimeExtender.setCallback(endLifetimeExtension) + entry = NotificationEntryBuilder().build() // Same summary we can use for either set of children - mGroupSummary = mHelper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500) + groupSummary = helper.createSummaryNotification(GROUP_ALERT_ALL, 0, "summary", 500) // One set of children with GROUP_ALERT_SUMMARY - mGroupPriority = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400) - mGroupSibling1 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300) - mGroupSibling2 = mHelper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200) + groupPriority = helper.createChildNotification(GROUP_ALERT_SUMMARY, 0, "priority", 400) + groupSibling1 = helper.createChildNotification(GROUP_ALERT_SUMMARY, 1, "sibling", 300) + groupSibling2 = helper.createChildNotification(GROUP_ALERT_SUMMARY, 2, "sibling", 200) // Another set of children with GROUP_ALERT_ALL - mGroupChild1 = mHelper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) - mGroupChild2 = mHelper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) - mGroupChild3 = mHelper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) + groupChild1 = helper.createChildNotification(GROUP_ALERT_ALL, 1, "child", 350) + groupChild2 = helper.createChildNotification(GROUP_ALERT_ALL, 2, "child", 250) + groupChild3 = helper.createChildNotification(GROUP_ALERT_ALL, 3, "child", 150) // Set the default FSI decision setShouldFullScreen(any(), FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) // Run tests with default feature flag state - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default) + whenever(flags.fsiOnDNDUpdate()).thenReturn(Flags.FSI_ON_DND_UPDATE.default) } @Test fun testCancelStickyNotification() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) - addHUN(mEntry) - whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(true)) + whenever(headsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(entry) + whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 0L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + executor.advanceClockToLast() + executor.runAllReady() + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true)) } @Test fun testCancelAndReAddStickyNotification() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) - addHUN(mEntry) - whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true, false) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - addHUN(mEntry) - assertFalse(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + whenever(headsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(entry) + whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false, true, false) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + addHUN(entry) + assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + executor.advanceClockToLast() + executor.runAllReady() + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) } @Test fun hunNotRemovedWhenExtensionCancelled() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) - addHUN(mEntry) - whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - mNotifLifetimeExtender.cancelLifetimeExtension(mEntry) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), any()) + whenever(headsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(entry) + whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + notifLifetimeExtender.cancelLifetimeExtension(entry) + executor.advanceClockToLast() + executor.runAllReady() + verify(headsUpManager, times(0)).removeNotification(anyString(), any()) } @Test fun hunExtensionCancelledWhenHunActionPressed() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) - addHUN(mEntry) - whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - mActionPressListener.accept(mEntry) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - verify(mHeadsUpManager, times(1)).removeNotification(eq(mEntry.key), eq(true)) + whenever(headsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(entry) + whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + actionPressListener.accept(entry) + executor.advanceClockToLast() + executor.runAllReady() + verify(headsUpManager, times(1)).removeNotification(eq(entry.key), eq(true)) } @Test fun testCancelUpdatedStickyNotification() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true) - addHUN(mEntry) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - addHUN(mEntry) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(false)) - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + whenever(headsUpManager.isSticky(anyString())).thenReturn(true) + addHUN(entry) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + addHUN(entry) + executor.advanceClockToLast() + executor.runAllReady() + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) } @Test fun testCancelNotification() { - whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(false) - addHUN(mEntry) - whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0)) - mExecutor.advanceClockToLast() - mExecutor.runAllReady() - verify(mHeadsUpManager, times(1)).removeNotification(anyString(), eq(false)) - verify(mHeadsUpManager, times(0)).removeNotification(anyString(), eq(true)) + whenever(headsUpManager.isSticky(anyString())).thenReturn(false) + addHUN(entry) + whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L, 500L) + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0)) + executor.advanceClockToLast() + executor.runAllReady() + verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false)) + verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true)) } @Test fun testOnEntryAdded_shouldFullScreen() { - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN) - mCollectionListener.onEntryAdded(mEntry) - verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN) + collectionListener.onEntryAdded(entry) + verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) } @Test fun testOnEntryAdded_shouldNotFullScreen() { - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) - mCollectionListener.onEntryAdded(mEntry) - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) + collectionListener.onEntryAdded(entry) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) } @Test fun testPromotesAddedHUN() { // GIVEN the current entry should heads up - whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + setShouldHeadsUp(entry, true) // WHEN the notification is added but not yet binding - mCollectionListener.onEntryAdded(mEntry) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) + collectionListener.onEntryAdded(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) // THEN only promote mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) } @Test fun testPromotesBindingHUN() { // GIVEN the current entry should heads up - whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + setShouldHeadsUp(entry, true) // WHEN the notification started binding on the previous run - mCollectionListener.onEntryAdded(mEntry) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), any()) + collectionListener.onEntryAdded(entry) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), any()) // THEN only promote mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) + assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) } @Test fun testPromotesCurrentHUN() { // GIVEN the current HUN is set to mEntry - addHUN(mEntry) + addHUN(entry) // THEN only promote the current HUN, mEntry - assertTrue(mNotifPromoter.shouldPromoteToTopLevel(mEntry)) - assertFalse(mNotifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() + assertTrue(notifPromoter.shouldPromoteToTopLevel(entry)) + assertFalse(notifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder() .setPkg("test-package2") .build())) } @@ -313,11 +313,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test fun testIncludeInSectionCurrentHUN() { // GIVEN the current HUN is set to mEntry - addHUN(mEntry) + addHUN(entry) // THEN only section the current HUN, mEntry - assertTrue(mNotifSectioner.isInSection(mEntry)) - assertFalse(mNotifSectioner.isInSection(NotificationEntryBuilder() + assertTrue(notifSectioner.isInSection(entry)) + assertFalse(notifSectioner.isInSection(NotificationEntryBuilder() .setPkg("test-package") .build())) } @@ -325,11 +325,11 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test fun testLifetimeExtendsCurrentHUN() { // GIVEN there is a HUN, mEntry - addHUN(mEntry) + addHUN(entry) // THEN only the current HUN, mEntry, should be lifetimeExtended - assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, /* cancellationReason */ 0)) - assertFalse(mNotifLifetimeExtender.maybeExtendLifetime( + assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* cancellationReason */ 0)) + assertFalse(notifLifetimeExtender.maybeExtendLifetime( NotificationEntryBuilder() .setPkg("test-package") .build(), /* cancellationReason */ 0)) @@ -338,726 +338,752 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { @Test fun testShowHUNOnInflationFinished() { // WHEN a notification should HUN and its inflation is finished - whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true) + setShouldHeadsUp(entry, true) - mCollectionListener.onEntryAdded(mEntry) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) - verify(mHeadsUpManager, never()).showNotification(mEntry) + collectionListener.onEntryAdded(entry) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) + verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(mEntry), capture()) - }.onBindFinished(mEntry) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + }.onBindFinished(entry) // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager).showNotification(mEntry) + verify(headsUpManager).showNotification(entry) } @Test fun testNoHUNOnInflationFinished() { // WHEN a notification shouldn't HUN and its inflation is finished - whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(false) - mCollectionListener.onEntryAdded(mEntry) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldHeadsUp(entry, false) + collectionListener.onEntryAdded(entry) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN we never bind the heads up view or tell HeadsUpManager to show the notification - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mEntry), any()) - verify(mHeadsUpManager, never()).showNotification(mEntry) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(entry), any()) + verify(headsUpManager, never()).showNotification(entry) } @Test fun testOnEntryUpdated_toAlert() { // GIVEN that an entry is posted that should not heads up - setShouldHeadsUp(mEntry, false) - mCollectionListener.onEntryAdded(mEntry) + setShouldHeadsUp(entry, false) + collectionListener.onEntryAdded(entry) // WHEN it's updated to heads up - setShouldHeadsUp(mEntry) - mCollectionListener.onEntryUpdated(mEntry) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldHeadsUp(entry) + collectionListener.onEntryUpdated(entry) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification alerts - finishBind(mEntry) - verify(mHeadsUpManager).showNotification(mEntry) + finishBind(entry) + verify(headsUpManager).showNotification(entry) } @Test fun testOnEntryUpdated_toNotAlert() { // GIVEN that an entry is posted that should heads up - setShouldHeadsUp(mEntry) - mCollectionListener.onEntryAdded(mEntry) + setShouldHeadsUp(entry) + collectionListener.onEntryAdded(entry) // WHEN it's updated to not heads up - setShouldHeadsUp(mEntry, false) - mCollectionListener.onEntryUpdated(mEntry) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldHeadsUp(entry, false) + collectionListener.onEntryUpdated(entry) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) } @Test fun testOnEntryRemovedRemovesHeadsUpNotification() { // GIVEN the current HUN is mEntry - addHUN(mEntry) + addHUN(entry) // WHEN mEntry is removed from the notification collection - mCollectionListener.onEntryRemoved(mEntry, /* cancellation reason */ 0) - whenever(mRemoteInputManager.isSpinning(any())).thenReturn(false) + collectionListener.onEntryRemoved(entry, /* cancellation reason */ 0) + whenever(remoteInputManager.isSpinning(any())).thenReturn(false) // THEN heads up manager should remove the entry - verify(mHeadsUpManager).removeNotification(mEntry.key, false) + verify(headsUpManager).removeNotification(entry.key, false) } private fun addHUN(entry: NotificationEntry) { - mHuns.add(entry) - whenever(mHeadsUpManager.topEntry).thenReturn(entry) - mOnHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) - mNotifLifetimeExtender.cancelLifetimeExtension(entry) + huns.add(entry) + whenever(headsUpManager.topEntry).thenReturn(entry) + onHeadsUpChangedListener.onHeadsUpStateChanged(entry, true) + notifLifetimeExtender.cancelLifetimeExtension(entry) } @Test fun testTransferIsolatedChildAlert_withGroupAlertSummary() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupSibling1)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupSibling1)) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupSibling1) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupSibling1)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupSibling1)) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupSibling1) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupSibling1)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupSibling1)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupSibling1) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupSibling1) + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupSibling1) // In addition make sure we have explicitly marked the summary as having interrupted due // to the alert being transferred - assertTrue(mGroupSummary.hasInterrupted()) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferIsolatedChildAlert_withGroupAlertAll() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mGroupSummary, mGroupChild1)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupChild1)) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupChild1) - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mGroupChild1)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mGroupChild1)) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupChild1) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupChild1)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupChild1)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupChild1) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupChild1) - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupChild1) - assertTrue(mGroupSummary.hasInterrupted()) + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupChild1) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferTwoIsolatedChildAlert_withGroupAlertSummary() { // WHEN a notification should HUN and its inflation is finished - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) - - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupSibling1) - mCollectionListener.onEntryAdded(mGroupSibling2) - val entryList = listOf(mGroupSibling1, mGroupSibling2) - mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) - - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupSibling1) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority)) + + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupSibling1) + collectionListener.onEntryAdded(groupSibling2) + val entryList = listOf(groupSibling1, groupSibling2) + beforeTransformGroupsListener.onBeforeTransformGroups(entryList) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) + + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupSibling1) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) - assertTrue(mGroupSummary.hasInterrupted()) + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferTwoIsolatedChildAlert_withGroupAlertAll() { // WHEN a notification should HUN and its inflation is finished - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2, mGroupPriority)) - - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupChild1) - mCollectionListener.onEntryAdded(mGroupChild2) - val entryList = listOf(mGroupChild1, mGroupChild2) - mBeforeTransformGroupsListener.onBeforeTransformGroups(entryList) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) - - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupChild1) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupChild1, groupChild2, groupPriority)) + + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupChild1) + collectionListener.onEntryAdded(groupChild2) + val entryList = listOf(groupChild1, groupChild2) + beforeTransformGroupsListener.onBeforeTransformGroups(entryList) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(entryList) + + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupChild1) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupChild1) - verify(mHeadsUpManager, never()).showNotification(mGroupChild2) - assertTrue(mGroupSummary.hasInterrupted()) + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupChild1) + verify(headsUpManager, never()).showNotification(groupChild2) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferToPriorityOnAddWithTwoSiblings() { // WHEN a notification should HUN and its inflation is finished - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority)) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupPriority) - mCollectionListener.onEntryAdded(mGroupSibling1) - mCollectionListener.onEntryAdded(mGroupSibling2) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupPriority) + collectionListener.onEntryAdded(groupSibling1) + collectionListener.onEntryAdded(groupSibling2) val beforeTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) val afterTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) .build() - mBeforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) + beforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupPriority) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupPriority) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) // THEN we tell the HeadsUpManager to show the notification - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupPriority) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) - assertTrue(mGroupSummary.hasInterrupted()) + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupPriority) + verify(headsUpManager, never()).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferToPriorityOnUpdateWithTwoSiblings() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority)) - mCollectionListener.onEntryUpdated(mGroupSummary) - mCollectionListener.onEntryUpdated(mGroupPriority) - mCollectionListener.onEntryUpdated(mGroupSibling1) - mCollectionListener.onEntryUpdated(mGroupSibling2) + collectionListener.onEntryUpdated(groupSummary) + collectionListener.onEntryUpdated(groupPriority) + collectionListener.onEntryUpdated(groupSibling1) + collectionListener.onEntryUpdated(groupSibling2) val beforeTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) val afterTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) .build() - mBeforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) - - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupPriority) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) - - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupPriority) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) - assertTrue(mGroupSummary.hasInterrupted()) + beforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupPriority) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupPriority) + verify(headsUpManager, never()).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testTransferToPriorityOnUpdateWithTwoNonUpdatedSiblings() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority)) - mCollectionListener.onEntryUpdated(mGroupSummary) - mCollectionListener.onEntryUpdated(mGroupPriority) + collectionListener.onEntryUpdated(groupSummary) + collectionListener.onEntryUpdated(groupPriority) val beforeTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) val afterTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) .build() - mBeforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) - - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupPriority) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) - - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupPriority) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) - assertTrue(mGroupSummary.hasInterrupted()) + beforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupPriority) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupPriority) + verify(headsUpManager, never()).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) + assertTrue(groupSummary.hasInterrupted()) } @Test fun testNoTransferToPriorityOnUpdateOfTwoSiblings() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2, mGroupPriority)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2, groupPriority)) - mCollectionListener.onEntryUpdated(mGroupSummary) - mCollectionListener.onEntryUpdated(mGroupSibling1) - mCollectionListener.onEntryUpdated(mGroupSibling2) + collectionListener.onEntryUpdated(groupSummary) + collectionListener.onEntryUpdated(groupSibling1) + collectionListener.onEntryUpdated(groupSibling2) val beforeTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupPriority, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupPriority, groupSibling2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) val afterTransformGroup = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) .build() - mBeforeFinalizeFilterListener - .onBeforeFinalizeFilter(listOf(mGroupPriority, afterTransformGroup)) - - finishBind(mGroupSummary) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupPriority), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) - - verify(mHeadsUpManager).showNotification(mGroupSummary) - verify(mHeadsUpManager, never()).showNotification(mGroupPriority) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + beforeFinalizeFilterListener + .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup)) + + finishBind(groupSummary) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) + + verify(headsUpManager).showNotification(groupSummary) + verify(headsUpManager, never()).showNotification(groupPriority) + verify(headsUpManager, never()).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) } @Test fun testNoTransferTwoChildAlert_withGroupAlertSummary() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupSibling1, mGroupSibling2)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupSibling1, groupSibling2)) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupSibling1) - mCollectionListener.onEntryAdded(mGroupSibling2) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupSibling1) + collectionListener.onEntryAdded(groupSibling2) val groupEntry = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupSibling1, mGroupSibling2)) + .setSummary(groupSummary) + .setChildren(listOf(groupSibling1, groupSibling2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) - finishBind(mGroupSummary) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSibling2), any()) + finishBind(groupSummary) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSibling2), any()) - verify(mHeadsUpManager).showNotification(mGroupSummary) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) - verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + verify(headsUpManager).showNotification(groupSummary) + verify(headsUpManager, never()).showNotification(groupSibling1) + verify(headsUpManager, never()).showNotification(groupSibling2) } @Test fun testNoTransferTwoChildAlert_withGroupAlertAll() { - setShouldHeadsUp(mGroupSummary) - whenever(mNotifPipeline.allNotifs) - .thenReturn(listOf(mGroupSummary, mGroupChild1, mGroupChild2)) + setShouldHeadsUp(groupSummary) + whenever(notifPipeline.allNotifs) + .thenReturn(listOf(groupSummary, groupChild1, groupChild2)) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupChild1) - mCollectionListener.onEntryAdded(mGroupChild2) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupChild1) + collectionListener.onEntryAdded(groupChild2) val groupEntry = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupChild1, mGroupChild2)) + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) - finishBind(mGroupSummary) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild1), any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) + finishBind(groupSummary) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild1), any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) - verify(mHeadsUpManager).showNotification(mGroupSummary) - verify(mHeadsUpManager, never()).showNotification(mGroupChild1) - verify(mHeadsUpManager, never()).showNotification(mGroupChild2) + verify(headsUpManager).showNotification(groupSummary) + verify(headsUpManager, never()).showNotification(groupChild1) + verify(headsUpManager, never()).showNotification(groupChild2) } @Test fun testNoTransfer_groupSummaryNotAlerting() { // When we have a group where the summary should not alert and exactly one child should // alert, we should never mark the group summary as interrupted (because it doesn't). - setShouldHeadsUp(mGroupSummary, false) - setShouldHeadsUp(mGroupChild1, true) - setShouldHeadsUp(mGroupChild2, false) + setShouldHeadsUp(groupSummary, false) + setShouldHeadsUp(groupChild1, true) + setShouldHeadsUp(groupChild2, false) - mCollectionListener.onEntryAdded(mGroupSummary) - mCollectionListener.onEntryAdded(mGroupChild1) - mCollectionListener.onEntryAdded(mGroupChild2) + collectionListener.onEntryAdded(groupSummary) + collectionListener.onEntryAdded(groupChild1) + collectionListener.onEntryAdded(groupChild2) val groupEntry = GroupEntryBuilder() - .setSummary(mGroupSummary) - .setChildren(listOf(mGroupChild1, mGroupChild2)) + .setSummary(groupSummary) + .setChildren(listOf(groupChild1, groupChild2)) .build() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) - - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) - finishBind(mGroupChild1) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) - - verify(mHeadsUpManager, never()).showNotification(mGroupSummary) - verify(mHeadsUpManager).showNotification(mGroupChild1) - verify(mHeadsUpManager, never()).showNotification(mGroupChild2) - assertFalse(mGroupSummary.hasInterrupted()) + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any()) + finishBind(groupChild1) + verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupChild2), any()) + + verify(headsUpManager, never()).showNotification(groupSummary) + verify(headsUpManager).showNotification(groupChild1) + verify(headsUpManager, never()).showNotification(groupChild2) + assertFalse(groupSummary.hasInterrupted()) } @Test fun testOnRankingApplied_newEntryShouldAlert() { // GIVEN that mEntry has never interrupted in the past, and now should // and is new enough to do so - assertFalse(mEntry.hasInterrupted()) - mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis()) - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + assertFalse(entry.hasInterrupted()) + coordinator.setUpdateTime(entry, systemClock.currentTimeMillis()) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) // WHEN a ranking applied update occurs - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is shown - finishBind(mEntry) - verify(mHeadsUpManager).showNotification(mEntry) + finishBind(entry) + verify(headsUpManager).showNotification(entry) } @Test fun testOnRankingApplied_alreadyAlertedEntryShouldNotAlertAgain() { // GIVEN that mEntry has alerted in the past, even if it's new - mEntry.setInterruption() - mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis()) - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + entry.setInterruption() + coordinator.setUpdateTime(entry, systemClock.currentTimeMillis()) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) // WHEN a ranking applied update occurs - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) } @Test fun testOnRankingApplied_entryUpdatedToHun() { // GIVEN that mEntry is added in a state where it should not HUN - setShouldHeadsUp(mEntry, false) - mCollectionListener.onEntryAdded(mEntry) + setShouldHeadsUp(entry, false) + collectionListener.onEntryAdded(entry) // and it is then updated such that it should now HUN - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) // WHEN a ranking applied update occurs - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is shown - finishBind(mEntry) - verify(mHeadsUpManager).showNotification(mEntry) + finishBind(entry) + verify(headsUpManager).showNotification(entry) } @Test fun testOnRankingApplied_entryUpdatedButTooOld() { // GIVEN that mEntry is added in a state where it should not HUN - setShouldHeadsUp(mEntry, false) - mCollectionListener.onEntryAdded(mEntry) + setShouldHeadsUp(entry, false) + collectionListener.onEntryAdded(entry) // and it was actually added 10s ago - mCoordinator.setUpdateTime(mEntry, mSystemClock.currentTimeMillis() - 10000) + coordinator.setUpdateTime(entry, systemClock.currentTimeMillis() - 10000) // WHEN it is updated to HUN and then a ranking update occurs - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN the notification is never bound or shown - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) } @Test fun onEntryAdded_whenLaunchingFSI_doesLogDecision() { // GIVEN A new notification can FSI - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - mCollectionListener.onEntryAdded(mEntry) - - verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( - mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + collectionListener.onEntryAdded(entry) + + verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE + ) } @Test fun onEntryAdded_whenNotLaunchingFSI_doesLogDecision() { // GIVEN A new notification can't FSI - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) + collectionListener.onEntryAdded(entry) - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( - mEntry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verifyLoggedFullScreenIntentDecision(entry, FullScreenIntentDecision.NO_FULL_SCREEN_INTENT) } @Test fun onEntryAdded_whenNotLaunchingFSIBecauseOfDnd_doesLogDecision() { // GIVEN A new notification can't FSI because of DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) - - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( - mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) + + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND + ) } @Test fun testOnRankingApplied_noFSIOnUpdateWhenFlagOff() { // Ensure the feature flag is off - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(false) + whenever(flags.fsiOnDNDUpdate()).thenReturn(false) // GIVEN that mEntry was previously suppressed from full-screen only by DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) // Verify that this causes a log - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision( - mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - clearInvocations(mNotificationInterruptStateProvider) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND + ) + clearInterruptionProviderInvocations() // and it is then updated to allow full screen - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) - mCollectionListener.onRankingApplied() + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) + collectionListener.onRankingApplied() // THEN it should not full screen because the feature is off - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) // VERIFY that no additional logging happens either - verify(mNotificationInterruptStateProvider, never()) - .logFullScreenIntentDecision(any(), any()) + verifyNoFullScreenIntentDecisionLogged() } @Test fun testOnRankingApplied_updateToFullScreen() { // Turn on the feature - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) + whenever(flags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) // at this point, it should not have full screened, but should have logged - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, - FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - clearInvocations(mNotificationInterruptStateProvider) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND + ) + clearInterruptionProviderInvocations() // and it is then updated to allow full screen AND HUN - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN it should full screen and log but it should NOT HUN - verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, - FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - clearInvocations(mNotificationInterruptStateProvider) + verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE + ) + clearInterruptionProviderInvocations() // WHEN ranking updates again and the pipeline reruns - clearInvocations(mLaunchFullScreenIntentProvider) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + clearInvocations(launchFullScreenIntentProvider) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // VERIFY that the FSI does not launch again or log - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mNotificationInterruptStateProvider, never()) - .logFullScreenIntentDecision(any(), any()) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verifyNoFullScreenIntentDecisionLogged() } @Test fun testOnRankingApplied_withOnlyDndSuppressionAllowsFsiLater() { // Turn on the feature - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) + whenever(flags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) // at this point, it should not have full screened, but should have logged - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, - FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - clearInvocations(mNotificationInterruptStateProvider) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND + ) + clearInterruptionProviderInvocations() // ranking is applied with only DND blocking FSI - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN it should still not yet full screen or HUN - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) // Same decision as before; is not logged - verify(mNotificationInterruptStateProvider, never()) - .logFullScreenIntentDecision(any(), any()) - clearInvocations(mNotificationInterruptStateProvider) + verifyNoFullScreenIntentDecisionLogged() + clearInterruptionProviderInvocations() // and it is then updated to allow full screen AND HUN - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - setShouldHeadsUp(mEntry) - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + setShouldHeadsUp(entry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN it should full screen and log but it should NOT HUN - verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) - verify(mNotificationInterruptStateProvider).logFullScreenIntentDecision(mEntry, - FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - clearInvocations(mNotificationInterruptStateProvider) + verify(launchFullScreenIntentProvider).launchFullScreenIntent(entry) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) + verifyLoggedFullScreenIntentDecision( + entry, + FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE + ) + clearInterruptionProviderInvocations() } @Test fun testOnRankingApplied_newNonFullScreenAnswerInvalidatesCandidate() { // Turn on the feature - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) + whenever(flags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry was previously suppressed from full-screen only by DND - whenever(mNotifPipeline.allNotifs).thenReturn(listOf(mEntry)) - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + whenever(notifPipeline.allNotifs).thenReturn(listOf(entry)) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) // at this point, it should not have full screened - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry) // now some other condition blocks FSI in addition to DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // THEN it should NOT full screen or HUN - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) - verify(mHeadsUpManager, never()).showNotification(any()) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any()) + verify(headsUpManager, never()).showNotification(any()) // NOW the DND logic changes and FSI and HUN are available - clearInvocations(mLaunchFullScreenIntentProvider) - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - setShouldHeadsUp(mEntry) - mCollectionListener.onRankingApplied() - mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(mEntry)) - mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(mEntry)) + clearInvocations(launchFullScreenIntentProvider) + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + setShouldHeadsUp(entry) + collectionListener.onRankingApplied() + beforeTransformGroupsListener.onBeforeTransformGroups(listOf(entry)) + beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry)) // VERIFY that the FSI didn't happen, but that we do HUN - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) - finishBind(mEntry) - verify(mHeadsUpManager).showNotification(mEntry) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(any()) + finishBind(entry) + verify(headsUpManager).showNotification(entry) } @Test fun testOnRankingApplied_noFSIWhenAlsoSuppressedForOtherReasons() { // Feature on - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) + whenever(flags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry is suppressed by DND (functionally), but not *only* DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND) + collectionListener.onEntryAdded(entry) // and it is updated to full screen later - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) - mCollectionListener.onRankingApplied() + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE) + collectionListener.onRankingApplied() // THEN it should still not full screen because something else was blocking it before - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry) } @Test fun testOnRankingApplied_noFSIWhenTooOld() { // Feature on - whenever(mFlags.fsiOnDNDUpdate()).thenReturn(true) + whenever(flags.fsiOnDNDUpdate()).thenReturn(true) // GIVEN that mEntry is suppressed only by DND - setShouldFullScreen(mEntry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) - mCollectionListener.onEntryAdded(mEntry) + setShouldFullScreen(entry, FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND) + collectionListener.onEntryAdded(entry) // but it's >10s old - mCoordinator.addForFSIReconsideration(mEntry, mSystemClock.currentTimeMillis() - 10000) + coordinator.addForFSIReconsideration(entry, systemClock.currentTimeMillis() - 10000) // and it is updated to full screen later - setShouldFullScreen(mEntry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN) - mCollectionListener.onRankingApplied() + setShouldFullScreen(entry, FullScreenIntentDecision.FSI_EXPECTED_NOT_TO_HUN) + collectionListener.onRankingApplied() // THEN it should still not full screen because it's too old - verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(mEntry) + verify(launchFullScreenIntentProvider, never()).launchFullScreenIntent(entry) } private fun setShouldHeadsUp(entry: NotificationEntry, should: Boolean = true) { - whenever(mNotificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should) - whenever(mNotificationInterruptStateProvider.checkHeadsUp(eq(entry), any())) + whenever(notificationInterruptStateProvider.shouldHeadsUp(entry)).thenReturn(should) + whenever(notificationInterruptStateProvider.checkHeadsUp(eq(entry), any())) .thenReturn(should) } private fun setShouldFullScreen(entry: NotificationEntry, decision: FullScreenIntentDecision) { - whenever(mNotificationInterruptStateProvider.getFullScreenIntentDecision(entry)) + whenever(notificationInterruptStateProvider.getFullScreenIntentDecision(entry)) .thenReturn(decision) } + private fun verifyLoggedFullScreenIntentDecision( + entry: NotificationEntry, + decision: FullScreenIntentDecision + ) { + verify(notificationInterruptStateProvider).logFullScreenIntentDecision(entry, decision) + } + + private fun verifyNoFullScreenIntentDecisionLogged() { + verify(notificationInterruptStateProvider, never()) + .logFullScreenIntentDecision(any(), any()) + } + + private fun clearInterruptionProviderInvocations() { + clearInvocations(notificationInterruptStateProvider) + } + private fun finishBind(entry: NotificationEntry) { - verify(mHeadsUpManager, never()).showNotification(entry) + verify(headsUpManager, never()).showNotification(entry) withArgCaptor<BindCallback> { - verify(mHeadsUpViewBinder).bindHeadsUpView(eq(entry), capture()) + verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture()) }.onBindFinished(entry) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 68d67ca20007..89f8bdbfe05b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -89,7 +89,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private KeyguardViewMediator mKeyguardViewMediator; @Mock - private BiometricUnlockController.BiometricModeListener mBiometricModeListener; + private BiometricUnlockController.BiometricUnlockEventsListener mBiometricUnlockEventsListener; @Mock private KeyguardStateController mKeyguardStateController; @Mock @@ -145,7 +145,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mSystemClock ); mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager); - mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener); + mBiometricUnlockController.addListener(mBiometricUnlockEventsListener); when(mUpdateMonitor.getStrongAuthTracker()).thenReturn(mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index 44fbd5b99894..6306a36d9730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel @@ -29,6 +30,7 @@ class FakeMobileConnectionRepository( override val subId: Int, override val tableLogBuffer: TableLogBuffer, ) : MobileConnectionRepository { + override val carrierId = MutableStateFlow(UNKNOWN_CARRIER_ID) override val isEmergencyOnly = MutableStateFlow(false) override val isRoaming = MutableStateFlow(false) override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index f2bb66a501ec..423c47661fa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -593,7 +593,6 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { val realRepo = MobileConnectionRepositoryImpl( - context, SUB_ID, defaultNetworkName = NetworkNameModel.Default("default"), networkNameSeparator = SEP, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index 934e1c64c6da..d1df6e3c2072 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -42,6 +42,7 @@ import android.telephony.TelephonyManager.DATA_SUSPENDED import android.telephony.TelephonyManager.DATA_UNKNOWN import android.telephony.TelephonyManager.ERI_OFF import android.telephony.TelephonyManager.ERI_ON +import android.telephony.TelephonyManager.EXTRA_CARRIER_ID import android.telephony.TelephonyManager.EXTRA_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN import android.telephony.TelephonyManager.EXTRA_SHOW_SPN @@ -116,7 +117,6 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { underTest = MobileConnectionRepositoryImpl( - context, SUB_1_ID, DEFAULT_NAME, SEP, @@ -359,6 +359,36 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test + fun carrierId_initialValueCaptured() = + testScope.runTest { + whenever(telephonyManager.simCarrierId).thenReturn(1234) + + var latest: Int? = null + val job = underTest.carrierId.onEach { latest = it }.launchIn(this) + + assertThat(latest).isEqualTo(1234) + + job.cancel() + } + + @Test + fun carrierId_updatesOnBroadcast() = + testScope.runTest { + whenever(telephonyManager.simCarrierId).thenReturn(1234) + + var latest: Int? = null + val job = underTest.carrierId.onEach { latest = it }.launchIn(this) + + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive(context, carrierIdIntent(carrierId = 4321)) + } + + assertThat(latest).isEqualTo(4321) + + job.cancel() + } + + @Test fun carrierNetworkChange() = testScope.runTest { var latest: Boolean? = null @@ -796,6 +826,15 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager) } + private fun carrierIdIntent( + subId: Int = SUB_1_ID, + carrierId: Int, + ): Intent = + Intent(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED).apply { + putExtra(EXTRA_SUBSCRIPTION_ID, subId) + putExtra(EXTRA_CARRIER_ID, carrierId) + } + private fun spnIntent( subId: Int = SUB_1_ID, showSpn: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt index 9da9ff72d380..4f15aed00230 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt @@ -117,7 +117,6 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() { underTest = MobileConnectionRepositoryImpl( - context, SUB_1_ID, DEFAULT_NAME, SEP, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index ddff17aef2de..9d294cf098e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -155,7 +155,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { connectionFactory = MobileConnectionRepositoryImpl.Factory( fakeBroadcastDispatcher, - context = context, telephonyManager = telephonyManager, bgDispatcher = IMMEDIATE, logger = logger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 8d2c5695c7c4..f054422e6524 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -17,11 +17,11 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength -import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.MutableStateFlow @@ -42,8 +42,10 @@ class FakeMobileIconInteractor( override val mobileIsDefault = MutableStateFlow(true) - private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G) - override val networkTypeIconGroup = _iconGroup + override val networkTypeIconGroup = + MutableStateFlow<NetworkTypeIconModel>( + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + ) override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo mode")) @@ -73,10 +75,6 @@ class FakeMobileIconInteractor( override val isForceHidden = MutableStateFlow(false) - fun setIconGroup(group: SignalIcon.MobileIconGroup) { - _iconGroup.value = group - } - fun setIsEmergencyOnly(emergency: Boolean) { _isEmergencyOnly.value = emergency } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index d6fdad417b31..3ced7b2c4e6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -59,7 +59,6 @@ class FakeMobileIconsInteractor( override val alwaysShowDataRatIcon = MutableStateFlow(false) override val alwaysUseCdmaLevel = MutableStateFlow(false) - override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID) override val mobileIsDefault = MutableStateFlow(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 2054e8b12eff..8d7f0f6035cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -19,7 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CellSignalStrength import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN import androidx.test.filters.SmallTest -import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.settingslib.mobile.MobileIconCarrierIdOverrides +import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState @@ -31,18 +32,24 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobile import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest class MobileIconInteractorTest : SysuiTestCase() { private lateinit var underTest: MobileIconInteractor @@ -50,29 +57,17 @@ class MobileIconInteractorTest : SysuiTestCase() { private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock()) private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock()) - private val scope = CoroutineScope(IMMEDIATE) + private val testDispatcher = UnconfinedTestDispatcher() + private val testScope = TestScope(testDispatcher) @Before fun setUp() { - underTest = - MobileIconInteractorImpl( - scope, - mobileIconsInteractor.activeDataConnectionHasDataEnabled, - mobileIconsInteractor.alwaysShowDataRatIcon, - mobileIconsInteractor.alwaysUseCdmaLevel, - mobileIconsInteractor.mobileIsDefault, - mobileIconsInteractor.defaultMobileIconMapping, - mobileIconsInteractor.defaultMobileIconGroup, - mobileIconsInteractor.defaultDataSubId, - mobileIconsInteractor.isDefaultConnectionFailed, - mobileIconsInteractor.isForceHidden, - connectionRepository, - ) + underTest = createInteractor() } @Test fun gsm_level_default_unknown() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = true var latest: Int? = null @@ -85,7 +80,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun gsm_usesGsmLevel() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = true connectionRepository.primaryLevel.value = GSM_LEVEL connectionRepository.cdmaLevel.value = CDMA_LEVEL @@ -100,7 +95,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = true connectionRepository.primaryLevel.value = GSM_LEVEL connectionRepository.cdmaLevel.value = CDMA_LEVEL @@ -116,7 +111,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun notGsm_level_default_unknown() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = false var latest: Int? = null @@ -128,7 +123,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = false connectionRepository.primaryLevel.value = GSM_LEVEL connectionRepository.cdmaLevel.value = CDMA_LEVEL @@ -144,7 +139,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.isGsm.value = false connectionRepository.primaryLevel.value = GSM_LEVEL connectionRepository.cdmaLevel.value = CDMA_LEVEL @@ -160,7 +155,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun numberOfLevels_comesFromRepo() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Int? = null val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) @@ -175,101 +170,106 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun iconGroup_three_g() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.resolvedNetworkType.value = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(TelephonyIcons.THREE_G) + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)) job.cancel() } @Test fun iconGroup_updates_on_change() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.resolvedNetworkType.value = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) connectionRepository.resolvedNetworkType.value = DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G)) - yield() - assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G) + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.FOUR_G)) job.cancel() } @Test fun iconGroup_5g_override_type() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.resolvedNetworkType.value = OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)) - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(TelephonyIcons.NR_5G) + assertThat(latest).isEqualTo(NetworkTypeIconModel.DefaultIcon(TelephonyIcons.NR_5G)) job.cancel() } @Test fun iconGroup_default_if_no_lookup() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.resolvedNetworkType.value = DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)) - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON) + assertThat(latest) + .isEqualTo(NetworkTypeIconModel.DefaultIcon(FakeMobileIconsInteractor.DEFAULT_ICON)) job.cancel() } @Test fun iconGroup_carrierMerged_usesOverride() = - runBlocking(IMMEDIATE) { + testScope.runTest { connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(CarrierMergedNetworkType.iconGroupOverride) + assertThat(latest) + .isEqualTo( + NetworkTypeIconModel.DefaultIcon(CarrierMergedNetworkType.iconGroupOverride) + ) job.cancel() } @Test - fun `icon group - checks default data`() = - runBlocking(IMMEDIATE) { - mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID + fun overrideIcon_usesCarrierIdOverride() = + testScope.runTest { + val overrides = + mock<MobileIconCarrierIdOverrides>().also { + whenever(it.carrierIdEntryExists(anyInt())).thenReturn(true) + whenever(it.getOverrideFor(anyInt(), anyString(), any())).thenReturn(1234) + } + + underTest = createInteractor(overrides) + connectionRepository.resolvedNetworkType.value = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G)) - var latest: MobileIconGroup? = null + var latest: NetworkTypeIconModel? = null val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this) - assertThat(latest).isEqualTo(TelephonyIcons.THREE_G) - - // Default data sub id changes to something else - mobileIconsInteractor.defaultDataSubId.value = 123 - yield() - - assertThat(latest).isEqualTo(TelephonyIcons.NOT_DEFAULT_DATA) + assertThat(latest) + .isEqualTo(NetworkTypeIconModel.OverriddenIcon(TelephonyIcons.THREE_G, 1234)) job.cancel() } @Test fun alwaysShowDataRatIcon_matchesParent() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) @@ -284,7 +284,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun alwaysUseCdmaLevel_matchesParent() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) @@ -299,7 +299,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun test_isDefaultDataEnabled_matchesParent() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this) @@ -314,7 +314,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun test_isDefaultConnectionFailed_matchedParent() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isDefaultConnectionFailed.launchIn(this) mobileIconsInteractor.isDefaultConnectionFailed.value = false @@ -328,12 +328,11 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun dataState_connected() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) connectionRepository.dataConnectionState.value = DataConnectionState.Connected - yield() assertThat(latest).isTrue() @@ -342,7 +341,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun dataState_notConnected() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) @@ -355,7 +354,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun `isInService - uses repository value`() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isInService.onEach { latest = it }.launchIn(this) @@ -372,19 +371,17 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun `roaming - is gsm - uses connection model`() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) connectionRepository.cdmaRoaming.value = true connectionRepository.isGsm.value = true connectionRepository.isRoaming.value = false - yield() assertThat(latest).isFalse() connectionRepository.isRoaming.value = true - yield() assertThat(latest).isTrue() @@ -393,21 +390,19 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun `roaming - is cdma - uses cdma roaming bit`() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) connectionRepository.cdmaRoaming.value = false connectionRepository.isGsm.value = false connectionRepository.isRoaming.value = true - yield() assertThat(latest).isFalse() connectionRepository.cdmaRoaming.value = true connectionRepository.isGsm.value = false connectionRepository.isRoaming.value = false - yield() assertThat(latest).isTrue() @@ -416,7 +411,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun `roaming - false while carrierNetworkChangeActive`() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isRoaming.onEach { latest = it }.launchIn(this) @@ -424,13 +419,11 @@ class MobileIconInteractorTest : SysuiTestCase() { connectionRepository.isGsm.value = false connectionRepository.isRoaming.value = true connectionRepository.carrierNetworkChangeActive.value = true - yield() assertThat(latest).isFalse() connectionRepository.cdmaRoaming.value = true connectionRepository.isGsm.value = true - yield() assertThat(latest).isFalse() @@ -439,7 +432,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun `network name - uses operatorAlphaShot when non null and repo is default`() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: NetworkNameModel? = null val job = underTest.networkName.onEach { latest = it }.launchIn(this) @@ -448,20 +441,17 @@ class MobileIconInteractorTest : SysuiTestCase() { // Default network name, operator name is non-null, uses the operator name connectionRepository.networkName.value = DEFAULT_NAME connectionRepository.operatorAlphaShort.value = testOperatorName - yield() assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName)) // Default network name, operator name is null, uses the default connectionRepository.operatorAlphaShort.value = null - yield() assertThat(latest).isEqualTo(DEFAULT_NAME) // Derived network name, operator name non-null, uses the derived name connectionRepository.networkName.value = DERIVED_NAME connectionRepository.operatorAlphaShort.value = testOperatorName - yield() assertThat(latest).isEqualTo(DERIVED_NAME) @@ -470,7 +460,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun isForceHidden_matchesParent() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: Boolean? = null val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) @@ -483,9 +473,25 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate + private fun createInteractor( + overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() + ) = + MobileIconInteractorImpl( + testScope.backgroundScope, + mobileIconsInteractor.activeDataConnectionHasDataEnabled, + mobileIconsInteractor.alwaysShowDataRatIcon, + mobileIconsInteractor.alwaysUseCdmaLevel, + mobileIconsInteractor.mobileIsDefault, + mobileIconsInteractor.defaultMobileIconMapping, + mobileIconsInteractor.defaultMobileIconGroup, + mobileIconsInteractor.isDefaultConnectionFailed, + mobileIconsInteractor.isForceHidden, + connectionRepository, + context, + overrides, + ) + companion object { private const val GSM_LEVEL = 1 private const val CDMA_LEVEL = 2 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 898e89770394..c5ceacac6446 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -88,6 +88,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectivityRepository, userSetupRepository, testScope.backgroundScope, + context, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index a6d915243f60..e99be864e73f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants @@ -71,9 +72,9 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { setLevel(1) setIsDefaultDataEnabled(true) setIsFailedConnection(false) - setIconGroup(TelephonyIcons.THREE_G) setIsEmergencyOnly(false) setNumberOfLevels(4) + networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) isDataConnected.value = true } commonImpl = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 8ea8f87e6aff..1b6ab4d7af96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH import com.android.settingslib.AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH_NONE import com.android.settingslib.mobile.TelephonyIcons.THREE_G +import com.android.settingslib.mobile.TelephonyIcons.UNKNOWN import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon @@ -27,6 +28,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel @@ -76,9 +78,9 @@ class MobileIconViewModelTest : SysuiTestCase() { setLevel(1) setIsDefaultDataEnabled(true) setIsFailedConnection(false) - setIconGroup(THREE_G) setIsEmergencyOnly(false) setNumberOfLevels(4) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) isDataConnected.value = true } createAndSetViewModel() @@ -255,7 +257,7 @@ class MobileIconViewModelTest : SysuiTestCase() { THREE_G.dataType, ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -266,10 +268,11 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun networkType_nullWhenDisabled() = + fun networkType_null_whenDisabled() = testScope.runTest { - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.setIsDataEnabled(false) + interactor.mobileIsDefault.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -279,15 +282,21 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun networkType_nullWhenFailedConnection() = + fun networkTypeIcon_notNull_whenEnabled() = testScope.runTest { - interactor.setIconGroup(THREE_G) + val expected = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription) + ) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.setIsDataEnabled(true) - interactor.setIsFailedConnection(true) + interactor.isDataConnected.value = true + interactor.mobileIsDefault.value = true var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) - assertThat(latest).isNull() + assertThat(latest).isEqualTo(expected) job.cancel() } @@ -301,11 +310,11 @@ class MobileIconViewModelTest : SysuiTestCase() { ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) assertThat(latest).isEqualTo(initial) interactor.isDataConnected.value = false @@ -324,7 +333,7 @@ class MobileIconViewModelTest : SysuiTestCase() { THREE_G.dataType, ContentDescription.Resource(THREE_G.dataContentDescription) ) - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.setIsDataEnabled(true) var latest: Icon? = null val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) @@ -342,8 +351,8 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisabled() = testScope.runTest { - interactor.setIconGroup(THREE_G) - interactor.setIsDataEnabled(true) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) + interactor.setIsDataEnabled(false) interactor.alwaysShowDataRatIcon.value = true var latest: Icon? = null @@ -362,7 +371,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenDisconnected() = testScope.runTest { - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.isDataConnected.value = false interactor.alwaysShowDataRatIcon.value = true @@ -382,7 +391,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun networkType_alwaysShow_shownEvenWhenFailedConnection() = testScope.runTest { - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.setIsFailedConnection(true) interactor.alwaysShowDataRatIcon.value = true @@ -400,9 +409,24 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test + fun networkType_alwaysShow_notShownWhenInvalidDataTypeIcon() = + testScope.runTest { + // The UNKNOWN icon group doesn't have a valid data type icon ID + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(UNKNOWN) + interactor.alwaysShowDataRatIcon.value = true + + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test fun `network type - alwaysShow - shown when not default`() = testScope.runTest { - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.mobileIsDefault.value = false interactor.alwaysShowDataRatIcon.value = true @@ -422,7 +446,7 @@ class MobileIconViewModelTest : SysuiTestCase() { @Test fun `network type - not shown when not default`() = testScope.runTest { - interactor.setIconGroup(THREE_G) + interactor.networkTypeIconGroup.value = NetworkTypeIconModel.DefaultIcon(THREE_G) interactor.isDataConnected.value = true interactor.mobileIsDefault.value = false diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 28bdca97552d..e82456524012 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -85,6 +85,9 @@ import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.launcher3.icons.BubbleBadgeIconFactory; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; @@ -127,11 +130,9 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.bubbles.Bubble; -import com.android.wm.shell.bubbles.BubbleBadgeIconFactory; import com.android.wm.shell.bubbles.BubbleData; import com.android.wm.shell.bubbles.BubbleDataRepository; import com.android.wm.shell.bubbles.BubbleEntry; -import com.android.wm.shell.bubbles.BubbleIconFactory; import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.BubbleViewInfoTask; @@ -1225,8 +1226,13 @@ public class BubblesTest extends SysuiTestCase { BubbleViewInfoTask.BubbleViewInfo info = BubbleViewInfoTask.BubbleViewInfo.populate(context, mBubbleController, mBubbleController.getStackView(), - new BubbleIconFactory(mContext), - new BubbleBadgeIconFactory(mContext), + new BubbleIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size)), + new BubbleBadgeIconFactory(mContext, + mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), + mContext.getResources().getColor(R.color.important_conversation), + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)), bubble, true /* skipInflation */); verify(userContext, times(1)).getPackageManager(); diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index 2188b99a3d83..2f3e4c075a5d 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.app.UiAutomation; import android.content.ComponentName; import android.content.Context; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -34,6 +35,7 @@ import android.view.Display; import android.view.accessibility.AccessibilityEvent; import com.android.internal.util.DumpUtils; +import com.android.server.utils.Slogf; import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; @@ -51,12 +53,8 @@ class UiAutomationManager { private UiAutomationService mUiAutomationService; - private AccessibilityServiceInfo mUiAutomationServiceInfo; - private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport; - private AccessibilityTrace mTrace; - private int mUiAutomationFlags; UiAutomationManager(Object lock) { @@ -97,9 +95,10 @@ class UiAutomationManager { WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerformer, AccessibilityWindowManager awm, int flags) { + accessibilityServiceInfo.setComponentName(COMPONENT_NAME); + Slogf.i(LOG_TAG, "Registering UiTestAutomationService (id=%s) when called by user %d", + accessibilityServiceInfo.getId(), Binder.getCallingUserHandle().getIdentifier()); synchronized (mLock) { - accessibilityServiceInfo.setComponentName(COMPONENT_NAME); - if (mUiAutomationService != null) { throw new IllegalStateException( "UiAutomationService " + mUiAutomationService.mServiceInterface @@ -116,7 +115,6 @@ class UiAutomationManager { mUiAutomationFlags = flags; mSystemSupport = systemSupport; - mTrace = trace; // Ignore registering UiAutomation if it is not allowed to use the accessibility // subsystem. if (!useAccessibility()) { @@ -126,7 +124,6 @@ class UiAutomationManager { mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerformer, awm); mUiAutomationServiceOwner = owner; - mUiAutomationServiceInfo = accessibilityServiceInfo; mUiAutomationService.mServiceInterface = serviceClient; try { mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService, 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 092eb4ea9014..d54aa7c101d7 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -301,25 +301,31 @@ public class CompanionTransportManager { int sdk = Build.VERSION.SDK_INT; String release = Build.VERSION.RELEASE; - if (Build.isDebuggable()) { - // Debug builds cannot pass attestation verification. Use hardcoded key instead. + + 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); + } 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 (remoteSdk == NON_ANDROID) { + } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) { + // 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); - } else if (sdk >= SECURE_CHANNEL_AVAILABLE_SDK - && remoteSdk >= SECURE_CHANNEL_AVAILABLE_SDK) { - Slog.i(TAG, "Creating a secure channel"); + } 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); - } else { - // 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 } addMessageListenersToTransport(transport); transport.start(); diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 1363ef31c68d..ae88f24ab409 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -126,7 +126,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final VirtualDeviceManagerService mService; private final PendingTrampolineCallback mPendingTrampolineCallback; private final int mOwnerUid; - private final int mDeviceId; + private int mDeviceId; // Thou shall not hold the mVirtualDeviceLock over the mInputController calls. // Holding the lock can lead to lock inversion with GlobalWindowManagerLock. // 1. After display is created the window manager calls into VDM during construction @@ -348,6 +348,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call public void launchPendingIntent(int displayId, PendingIntent pendingIntent, ResultReceiver resultReceiver) { + Objects.requireNonNull(pendingIntent); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(displayId)) { throw new SecurityException("Display ID " + displayId @@ -404,6 +405,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub super.close_enforcePermission(); // Remove about-to-be-closed virtual device from the service before butchering it. mService.removeVirtualDevice(mDeviceId); + mDeviceId = Context.DEVICE_ID_INVALID; VirtualDisplayWrapper[] virtualDisplaysToBeReleased; synchronized (mVirtualDeviceLock) { @@ -497,6 +499,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) { super.createVirtualDpad_enforcePermission(); + Objects.requireNonNull(config); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( @@ -517,6 +520,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) { super.createVirtualKeyboard_enforcePermission(); + Objects.requireNonNull(config); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( @@ -539,6 +543,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) { super.createVirtualMouse_enforcePermission(); + Objects.requireNonNull(config); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( @@ -560,6 +565,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void createVirtualTouchscreen(VirtualTouchscreenConfig config, @NonNull IBinder deviceToken) { super.createVirtualTouchscreen_enforcePermission(); + Objects.requireNonNull(config); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( @@ -590,6 +596,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config, @NonNull IBinder deviceToken) { super.createVirtualNavigationTouchpad_enforcePermission(); + Objects.requireNonNull(config); synchronized (mVirtualDeviceLock) { if (!mVirtualDisplays.contains(config.getAssociatedDisplayId())) { throw new SecurityException( diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 3b1983f55fb6..291c05877c17 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -66,7 +66,10 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -86,6 +89,14 @@ public class VirtualDeviceManagerService extends SystemService { private static AtomicInteger sNextUniqueIndex = new AtomicInteger( Context.DEVICE_ID_DEFAULT + 1); + private final CompanionDeviceManager.OnAssociationsChangedListener mCdmAssociationListener = + new CompanionDeviceManager.OnAssociationsChangedListener() { + @Override + public void onAssociationsChanged(@NonNull List<AssociationInfo> associations) { + syncVirtualDevicesToCdmAssociations(associations); + } + }; + /** * Mapping from device IDs to virtual devices. */ @@ -204,11 +215,56 @@ public class VirtualDeviceManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { getContext().sendBroadcastAsUser(i, UserHandle.ALL); + + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + unregisterCdmAssociationListener(); + } + } } finally { Binder.restoreCallingIdentity(identity); } } + private void syncVirtualDevicesToCdmAssociations(List<AssociationInfo> associations) { + Set<VirtualDeviceImpl> virtualDevicesToRemove = new HashSet<>(); + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + return; + } + + Set<Integer> activeAssociationIds = new HashSet<>(associations.size()); + for (AssociationInfo association : associations) { + activeAssociationIds.add(association.getId()); + } + + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); + if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) { + virtualDevicesToRemove.add(virtualDevice); + } + } + } + + for (VirtualDeviceImpl virtualDevice : virtualDevicesToRemove) { + virtualDevice.close(); + } + + } + + private void registerCdmAssociationListener() { + final CompanionDeviceManager cdm = getContext().getSystemService( + CompanionDeviceManager.class); + cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(), + mCdmAssociationListener); + } + + private void unregisterCdmAssociationListener() { + final CompanionDeviceManager cdm = getContext().getSystemService( + CompanionDeviceManager.class); + cdm.removeOnAssociationsChangedListener(mCdmAssociationListener); + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback = @@ -254,7 +310,20 @@ public class VirtualDeviceManagerService extends SystemService { if (associationInfo == null) { throw new IllegalArgumentException("No association with ID " + associationId); } + Objects.requireNonNull(params); + Objects.requireNonNull(activityListener); + Objects.requireNonNull(soundEffectListener); + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.size() == 0) { + final long callindId = Binder.clearCallingIdentity(); + try { + registerCdmAssociationListener(); + } finally { + Binder.restoreCallingIdentity(callindId); + } + } + final UserHandle userHandle = getCallingUserHandle(); final CameraAccessController cameraAccessController = getCameraAccessController(userHandle); @@ -275,6 +344,7 @@ public class VirtualDeviceManagerService extends SystemService { public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig, IVirtualDisplayCallback callback, IVirtualDevice virtualDevice, String packageName) throws RemoteException { + Objects.requireNonNull(virtualDisplayConfig); final int callingUid = getCallingUid(); if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) { throw new SecurityException( diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index c5eb25b9981e..3fd6fe8afba6 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -26,8 +26,8 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.IActivityManager; -import android.app.IUidObserver; import android.app.SearchManager; +import android.app.UidObserver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -360,35 +360,18 @@ public final class PinnerService extends SystemService { private void registerUidListener() { try { - mAm.registerUidObserver(new IUidObserver.Stub() { + mAm.registerUidObserver(new UidObserver() { @Override - public void onUidGone(int uid, boolean disabled) throws RemoteException { + public void onUidGone(int uid, boolean disabled) { mPinnerHandler.sendMessage(PooledLambda.obtainMessage( PinnerService::handleUidGone, PinnerService.this, uid)); } @Override - public void onUidActive(int uid) throws RemoteException { + public void onUidActive(int uid) { mPinnerHandler.sendMessage(PooledLambda.obtainMessage( PinnerService::handleUidActive, PinnerService.this, uid)); } - - @Override - public void onUidIdle(int uid, boolean disabled) throws RemoteException { - } - - @Override - public void onUidStateChanged(int uid, int procState, long procStateSeq, - int capability) throws RemoteException { - } - - @Override - public void onUidCachedChanged(int uid, boolean cached) throws RemoteException { - } - - @Override - public void onUidProcAdjChanged(int uid) throws RemoteException { - } }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null); } catch (RemoteException e) { Slog.e(TAG, "Failed to register uid observer", e); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 3f68ccc64787..ae5dbe11495a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -151,6 +151,7 @@ final class ActivityManagerConstants extends ContentObserver { static final String KEY_USE_TIERED_CACHED_ADJ = "use_tiered_cached_adj"; static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time"; + static final String KEY_USE_MODERN_TRIM = "use_modern_trim"; private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024; private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true; @@ -212,6 +213,8 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false; private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000; + private static final boolean DEFAULT_USE_MODERN_TRIM = false; + /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} */ @@ -1052,6 +1055,9 @@ final class ActivityManagerConstants extends ContentObserver { /** @see #KEY_TIERED_CACHED_ADJ_DECAY_TIME */ public long TIERED_CACHED_ADJ_DECAY_TIME = DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME; + /** @see #KEY_USE_MODERN_TRIM */ + public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM; + private final OnPropertiesChangedListener mOnDeviceConfigChangedListener = new OnPropertiesChangedListener() { @Override @@ -1227,6 +1233,9 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_TIERED_CACHED_ADJ_DECAY_TIME: updateUseTieredCachedAdj(); break; + case KEY_USE_MODERN_TRIM: + updateUseModernTrim(); + break; default: updateFGSPermissionEnforcementFlagsIfNecessary(name); break; @@ -1997,6 +2006,13 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME); } + private void updateUseModernTrim() { + USE_MODERN_TRIM = DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_USE_MODERN_TRIM, + DEFAULT_USE_MODERN_TRIM); + } + private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) { ForegroundServiceTypePolicy.getDefaultPolicy() .updatePermissionEnforcementFlagIfNecessary(name); diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 350ac3bd4360..e080a80e1c37 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -59,12 +59,12 @@ import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.IProcessObserver; import android.app.IStopUserCallback; -import android.app.IUidObserver; import android.app.IUserSwitchObserver; import android.app.KeyguardManager; import android.app.ProcessStateEnum; import android.app.ProfilerInfo; import android.app.RemoteServiceException.CrashedByAdbException; +import android.app.UidObserver; import android.app.UserSwitchObserver; import android.app.WaitResult; import android.app.usage.AppStandbyInfo; @@ -1858,7 +1858,7 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } - static final class MyUidObserver extends IUidObserver.Stub + static final class MyUidObserver extends UidObserver implements ActivityManagerService.OomAdjObserver { final IActivityManager mInterface; final ActivityManagerService mInternal; @@ -1883,8 +1883,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) - throws RemoteException { + public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -1903,7 +1902,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidGone(int uid, boolean disabled) throws RemoteException { + public void onUidGone(int uid, boolean disabled) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -1921,7 +1920,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidActive(int uid) throws RemoteException { + public void onUidActive(int uid) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -1935,7 +1934,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidIdle(int uid, boolean disabled) throws RemoteException { + public void onUidIdle(int uid, boolean disabled) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -1953,7 +1952,7 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidCachedChanged(int uid, boolean cached) throws RemoteException { + public void onUidCachedChanged(int uid, boolean cached) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -1967,10 +1966,6 @@ final class ActivityManagerShellCommand extends ShellCommand { } @Override - public void onUidProcAdjChanged(int uid) throws RemoteException { - } - - @Override public void onOomAdjMessage(String msg) { synchronized (this) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 08c1de61d7fb..c343ec24412a 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -534,6 +534,8 @@ class AppErrors { } } + mService.mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(initialPid, + CachedAppOptimizer.UNFREEZE_REASON_PROCESS_END); proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java index ccd739006713..25ac956d328d 100644 --- a/services/core/java/com/android/server/am/AppProfiler.java +++ b/services/core/java/com/android/server/am/AppProfiler.java @@ -1005,6 +1005,37 @@ public class AppProfiler { mBgHandler.obtainMessage(BgHandler.MEMORY_PRESSURE_CHANGED, mLastMemoryLevel, memFactor) .sendToTarget(); } + + if (mService.mConstants.USE_MODERN_TRIM) { + // Modern trim is not sent based on lowmem state + // Dispatch UI_HIDDEN to processes that need it + mService.mProcessList.forEachLruProcessesLOSP(true, app -> { + final ProcessProfileRecord profile = app.mProfile; + final IApplicationThread thread; + final ProcessStateRecord state = app.mState; + if (state.hasProcStateChanged()) { + state.setProcStateChanged(false); + } + int procState = app.mState.getCurProcState(); + if (((procState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + && procState < ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) + || app.mState.isSystemNoUi()) && app.mProfile.hasPendingUiClean()) { + // If this application is now in the background and it + // had done UI, then give it the special trim level to + // have it free UI resources. + if ((thread = app.getThread()) != null) { + try { + thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + app.mProfile.setPendingUiClean(false); + } catch (RemoteException e) { + + } + } + } + }); + return false; + } + mLastMemoryLevel = memFactor; mLastNumProcesses = mService.mProcessList.getLruSizeLOSP(); boolean allChanged; diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index d5b8bb44e853..dbb351b23c85 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -1050,13 +1050,13 @@ class BroadcastProcessQueue { * Check overall health, confirming things are in a reasonable state and * that we're not wedged. */ - public void checkHealthLocked() { - checkHealthLocked(mPending); - checkHealthLocked(mPendingUrgent); - checkHealthLocked(mPendingOffload); + public void assertHealthLocked() { + assertHealthLocked(mPending); + assertHealthLocked(mPendingUrgent); + assertHealthLocked(mPendingOffload); } - private void checkHealthLocked(@NonNull ArrayDeque<SomeArgs> queue) { + private void assertHealthLocked(@NonNull ArrayDeque<SomeArgs> queue) { if (queue.isEmpty()) return; final Iterator<SomeArgs> it = queue.descendingIterator(); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index b18997a8d21f..a4bdf61e628f 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -246,21 +246,15 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private final Handler.Callback mLocalCallback = (msg) -> { switch (msg.what) { case MSG_UPDATE_RUNNING_LIST: { - synchronized (mService) { - updateRunningListLocked(); - } + updateRunningList(); return true; } case MSG_DELIVERY_TIMEOUT_SOFT: { - synchronized (mService) { - deliveryTimeoutSoftLocked((BroadcastProcessQueue) msg.obj, msg.arg1); - } + deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1); return true; } case MSG_DELIVERY_TIMEOUT_HARD: { - synchronized (mService) { - deliveryTimeoutHardLocked((BroadcastProcessQueue) msg.obj); - } + deliveryTimeoutHard((BroadcastProcessQueue) msg.obj); return true; } case MSG_BG_ACTIVITY_START_TIMEOUT: { @@ -274,9 +268,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return true; } case MSG_CHECK_HEALTH: { - synchronized (mService) { - checkHealthLocked(); - } + checkHealth(); return true; } } @@ -365,6 +357,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void updateRunningList() { + synchronized (mService) { + updateRunningListLocked(); + } + } + /** * Consider updating the list of "running" queues. * <p> @@ -965,6 +963,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultTo = null; } + private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue, + int softTimeoutMillis) { + synchronized (mService) { + deliveryTimeoutSoftLocked(queue, softTimeoutMillis); + } + } + private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue, int softTimeoutMillis) { if (queue.app != null) { @@ -981,6 +986,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) { + synchronized (mService) { + deliveryTimeoutHardLocked(queue); + } + } + private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, "deliveryTimeoutHardLocked"); @@ -1458,58 +1469,69 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: implement } + private void checkHealth() { + synchronized (mService) { + checkHealthLocked(); + } + } + + private void checkHealthLocked() { + try { + assertHealthLocked(); + + // If no health issues found above, check again in the future + mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH, + DateUtils.MINUTE_IN_MILLIS); + + } catch (Exception e) { + // Throw up a message to indicate that something went wrong, and + // dump current state for later inspection + Slog.wtf(TAG, e); + dumpToDropBoxLocked(e.toString()); + } + } + /** * Check overall health, confirming things are in a reasonable state and * that we're not wedged. If we determine we're in an unhealthy state, dump * current state once and stop future health checks to avoid spamming. */ @VisibleForTesting - void checkHealthLocked() { - try { - // Verify all runnable queues are sorted - BroadcastProcessQueue prev = null; - BroadcastProcessQueue next = mRunnableHead; - while (next != null) { - checkState(next.runnableAtPrev == prev, "runnableAtPrev"); - checkState(next.isRunnable(), "isRunnable " + next); - if (prev != null) { - checkState(next.getRunnableAt() >= prev.getRunnableAt(), - "getRunnableAt " + next + " vs " + prev); - } - prev = next; - next = next.runnableAtNext; + void assertHealthLocked() { + // Verify all runnable queues are sorted + BroadcastProcessQueue prev = null; + BroadcastProcessQueue next = mRunnableHead; + while (next != null) { + checkState(next.runnableAtPrev == prev, "runnableAtPrev"); + checkState(next.isRunnable(), "isRunnable " + next); + if (prev != null) { + checkState(next.getRunnableAt() >= prev.getRunnableAt(), + "getRunnableAt " + next + " vs " + prev); } + prev = next; + next = next.runnableAtNext; + } - // Verify all running queues are active - for (BroadcastProcessQueue queue : mRunning) { - if (queue != null) { - checkState(queue.isActive(), "isActive " + queue); - } + // Verify all running queues are active + for (BroadcastProcessQueue queue : mRunning) { + if (queue != null) { + checkState(queue.isActive(), "isActive " + queue); } + } - // Verify that pending cold start hasn't been orphaned - if (mRunningColdStart != null) { - checkState(getRunningIndexOf(mRunningColdStart) >= 0, - "isOrphaned " + mRunningColdStart); - } + // Verify that pending cold start hasn't been orphaned + if (mRunningColdStart != null) { + checkState(getRunningIndexOf(mRunningColdStart) >= 0, + "isOrphaned " + mRunningColdStart); + } - // Verify health of all known process queues - for (int i = 0; i < mProcessQueues.size(); i++) { - BroadcastProcessQueue leaf = mProcessQueues.valueAt(i); - while (leaf != null) { - leaf.checkHealthLocked(); - leaf = leaf.processNameNext; - } + // Verify health of all known process queues + for (int i = 0; i < mProcessQueues.size(); i++) { + BroadcastProcessQueue leaf = mProcessQueues.valueAt(i); + while (leaf != null) { + leaf.assertHealthLocked(); + leaf = leaf.processNameNext; } - - // If no health issues found above, check again in the future - mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_HEALTH, DateUtils.MINUTE_IN_MILLIS); - - } catch (Exception e) { - // Throw up a message to indicate that something went wrong, and - // dump current state for later inspection - Slog.wtf(TAG, e); - dumpToDropBoxLocked(e.toString()); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 9c1546324e4b..78edbba2e569 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; +import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FREEZER; @@ -27,12 +28,14 @@ import android.annotation.IntDef; import android.app.ActivityManager; import android.app.ActivityThread; import android.app.ApplicationExitInfo; +import android.app.IApplicationThread; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.os.PowerManagerInternal; import android.os.Process; +import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; import android.provider.DeviceConfig; @@ -1231,6 +1234,17 @@ public final class CachedAppOptimizer { return; } + if (mAm.mConstants.USE_MODERN_TRIM + && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) { + final IApplicationThread thread = app.getThread(); + if (thread != null) { + try { + thread.scheduleTrimMemory(TRIM_MEMORY_BACKGROUND); + } catch (RemoteException e) { + // do nothing + } + } + } mFreezeHandler.sendMessageDelayed( mFreezeHandler.obtainMessage( SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index af5609a68952..299f86550efd 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -75,10 +75,11 @@ public class AutomaticBrightnessController { private static final int MSG_UPDATE_AMBIENT_LUX = 1; private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2; - private static final int MSG_INVALIDATE_SHORT_TERM_MODEL = 3; + private static final int MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL = 3; private static final int MSG_UPDATE_FOREGROUND_APP = 4; private static final int MSG_UPDATE_FOREGROUND_APP_SYNC = 5; private static final int MSG_RUN_UPDATE = 6; + private static final int MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL = 7; // Callbacks for requesting updates to the display's power state private final Callbacks mCallbacks; @@ -216,12 +217,11 @@ public class AutomaticBrightnessController { private float mBrightnessAdjustmentSampleOldLux; private float mBrightnessAdjustmentSampleOldBrightness; - // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the - // user's adjustment) immediately, but wait for a drastic enough change in the ambient light. - // The anchor determines what were the light levels when the user has set their preference, and - // we use a relative threshold to determine when to revert to the OEM curve. - private boolean mShortTermModelValid; - private float mShortTermModelAnchor; + // The short term models, current and previous. Eg, we might use the "paused" one to save out + // the interactive short term model when switching to idle screen brightness mode, and + // vice-versa. + private final ShortTermModel mShortTermModel; + private final ShortTermModel mPausedShortTermModel; // Controls High Brightness Mode. private HighBrightnessModeController mHbmController; @@ -309,8 +309,8 @@ public class AutomaticBrightnessController { mAmbientBrightnessThresholdsIdle = ambientBrightnessThresholdsIdle; mScreenBrightnessThresholds = screenBrightnessThresholds; mScreenBrightnessThresholdsIdle = screenBrightnessThresholdsIdle; - mShortTermModelValid = true; - mShortTermModelAnchor = -1; + mShortTermModel = new ShortTermModel(); + mPausedShortTermModel = new ShortTermModel(); mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = new AmbientLightRingBuffer(mNormalLightSensorRate, mAmbientLightHorizonLong, mClock); @@ -492,10 +492,10 @@ public class AutomaticBrightnessController { Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy); } if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) { - mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL, + mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL, mCurrentBrightnessMapper.getShortTermModelTimeout()); } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) { - mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL); + mHandler.removeMessages(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL); } return true; } @@ -516,25 +516,13 @@ public class AutomaticBrightnessController { private boolean setScreenBrightnessByUser(float lux, float brightness) { mCurrentBrightnessMapper.addUserDataPoint(lux, brightness); - mShortTermModelValid = true; - mShortTermModelAnchor = lux; - if (mLoggingEnabled) { - Slog.d(TAG, "ShortTermModel: anchor=" + mShortTermModelAnchor); - } + mShortTermModel.setUserBrightness(lux, brightness); return true; } public void resetShortTermModel() { mCurrentBrightnessMapper.clearUserDataPoints(); - mShortTermModelValid = true; - mShortTermModelAnchor = -1; - } - - private void invalidateShortTermModel() { - if (mLoggingEnabled) { - Slog.d(TAG, "ShortTermModel: invalidate user data"); - } - mShortTermModelValid = false; + mShortTermModel.reset(); } public boolean setBrightnessConfiguration(BrightnessConfiguration configuration, @@ -595,8 +583,12 @@ public class AutomaticBrightnessController { pw.println(" mShortTermModelTimeout(idle)=" + mIdleModeBrightnessMapper.getShortTermModelTimeout()); } - pw.println(" mShortTermModelAnchor=" + mShortTermModelAnchor); - pw.println(" mShortTermModelValid=" + mShortTermModelValid); + pw.println(" mShortTermModel="); + mShortTermModel.dump(pw); + pw.println(" mPausedShortTermModel="); + mPausedShortTermModel.dump(pw); + + pw.println(); pw.println(" mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending); pw.println(" mBrightnessAdjustmentSampleOldLux=" + mBrightnessAdjustmentSampleOldLux); pw.println(" mBrightnessAdjustmentSampleOldBrightness=" @@ -740,15 +732,9 @@ public class AutomaticBrightnessController { } mHbmController.onAmbientLuxChange(mAmbientLux); + // If the short term model was invalidated and the change is drastic enough, reset it. - if (!mShortTermModelValid && mShortTermModelAnchor != -1) { - if (mCurrentBrightnessMapper.shouldResetShortTermModel( - mAmbientLux, mShortTermModelAnchor)) { - resetShortTermModel(); - } else { - mShortTermModelValid = true; - } - } + mShortTermModel.maybeReset(mAmbientLux); } private float calculateAmbientLux(long now, long horizon) { @@ -1118,8 +1104,29 @@ public class AutomaticBrightnessController { return; } Slog.i(TAG, "Switching to Idle Screen Brightness Mode"); + // Stash short term model + ShortTermModel tempShortTermModel = new ShortTermModel(); + tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(), + mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true); + + // Send delayed timeout + mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL, + mClock.uptimeMillis() + + mCurrentBrightnessMapper.getShortTermModelTimeout()); + + Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel); + // new brightness mapper mCurrentBrightnessMapper = mIdleModeBrightnessMapper; - resetShortTermModel(); + + // if previous stm has been invalidated, and lux has drastically changed, just use + // the new, reset stm. + // if previous stm is still valid then revalidate it + if (mPausedShortTermModel != null && !mPausedShortTermModel.maybeReset(mAmbientLux)) { + setScreenBrightnessByUser(mPausedShortTermModel.mAnchor, + mPausedShortTermModel.mBrightness); + } + mPausedShortTermModel.copyFrom(tempShortTermModel); + update(); } @@ -1128,8 +1135,28 @@ public class AutomaticBrightnessController { return; } Slog.i(TAG, "Switching to Interactive Screen Brightness Mode"); + ShortTermModel tempShortTermModel = new ShortTermModel(); + tempShortTermModel.set(mCurrentBrightnessMapper.getUserLux(), + mCurrentBrightnessMapper.getUserBrightness(), /* valid= */ true); + mHandler.removeMessages(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL); + // Send delayed timeout + mHandler.sendEmptyMessageAtTime(MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL, + mClock.uptimeMillis() + + mCurrentBrightnessMapper.getShortTermModelTimeout()); + Slog.i(TAG, "mPreviousShortTermModel" + mPausedShortTermModel.toString()); + + // restore interactive mapper. mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper; - resetShortTermModel(); + + // if previous stm has been invalidated, and lux has drastically changed, just use + // the new, reset stm. + // if previous stm is still valid then revalidate it + if (!mPausedShortTermModel.maybeReset(mAmbientLux)) { + setScreenBrightnessByUser(mPausedShortTermModel.mAnchor, + mPausedShortTermModel.mBrightness); + } + mPausedShortTermModel.copyFrom(tempShortTermModel); + update(); } @@ -1164,6 +1191,77 @@ public class AutomaticBrightnessController { } } + private class ShortTermModel { + // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the + // user's adjustment) immediately, but wait for a drastic enough change in the ambient + // light. + // The anchor determines what were the light levels when the user has set their preference, + // and we use a relative threshold to determine when to revert to the OEM curve. + private float mAnchor = -1f; + private float mBrightness; + private boolean mIsValid = true; + + private void reset() { + mAnchor = -1f; + mBrightness = -1f; + mIsValid = true; + } + + private void invalidate() { + mIsValid = false; + if (mLoggingEnabled) { + Slog.d(TAG, "ShortTermModel: invalidate user data"); + } + } + + private void setUserBrightness(float lux, float brightness) { + mAnchor = lux; + mBrightness = brightness; + mIsValid = true; + if (mLoggingEnabled) { + Slog.d(TAG, "ShortTermModel: anchor=" + mAnchor); + } + } + + private boolean maybeReset(float currentLux) { + // If the short term model was invalidated and the change is drastic enough, reset it. + // Otherwise, we revalidate it. + if (!mIsValid && mAnchor != -1) { + if (mCurrentBrightnessMapper != null + && mCurrentBrightnessMapper.shouldResetShortTermModel( + currentLux, mAnchor)) { + resetShortTermModel(); + } else { + mIsValid = true; + } + return mIsValid; + } + return false; + } + + private void set(float anchor, float brightness, boolean valid) { + mAnchor = anchor; + mBrightness = brightness; + mIsValid = valid; + } + private void copyFrom(ShortTermModel from) { + mAnchor = from.mAnchor; + mBrightness = from.mBrightness; + mIsValid = from.mIsValid; + } + + public String toString() { + return " mAnchor: " + mAnchor + + "\n mBrightness: " + mBrightness + + "\n mIsValid: " + mIsValid; + } + + void dump(PrintWriter pw) { + pw.println(this); + } + + } + private final class AutomaticBrightnessHandler extends Handler { public AutomaticBrightnessHandler(Looper looper) { super(looper, null, true /*async*/); @@ -1184,8 +1282,8 @@ public class AutomaticBrightnessController { collectBrightnessAdjustmentSample(); break; - case MSG_INVALIDATE_SHORT_TERM_MODEL: - invalidateShortTermModel(); + case MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL: + mShortTermModel.invalidate(); break; case MSG_UPDATE_FOREGROUND_APP: @@ -1195,6 +1293,10 @@ public class AutomaticBrightnessController { case MSG_UPDATE_FOREGROUND_APP_SYNC: updateForegroundAppSync(); break; + + case MSG_INVALIDATE_PAUSED_SHORT_TERM_MODEL: + mPausedShortTermModel.invalidate(); + break; } } } diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index d0471837d79b..3456e3e262de 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -363,13 +363,17 @@ public abstract class BrightnessMappingStrategy { public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment); /** - * Returns the timeout for the short term model + * Returns the timeout, in milliseconds for the short term model * * Timeout after which we remove the effects any user interactions might've had on the * brightness mapping. This timeout doesn't start until we transition to a non-interactive * display policy so that we don't reset while users are using their devices, but also so that * we don't erroneously keep the short-term model if the device is dozing but the * display is fully on. + * + * This timeout is also used when the device switches from interactive screen brightness mode + * to idle screen brightness mode, to preserve the user's preference when they resume usage of + * the device, within the specified timeframe. */ public abstract long getShortTermModelTimeout(); diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index eccee52f37aa..cfdcd636904b 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -16,7 +16,10 @@ package com.android.server.display; +import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID; + import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; @@ -33,8 +36,8 @@ import android.provider.DeviceConfigInterface; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; +import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; import java.io.PrintWriter; import java.util.ArrayList; @@ -63,49 +66,69 @@ class BrightnessThrottler { private final DeviceConfigInterface mDeviceConfig; private int mThrottlingStatus; - private BrightnessThrottlingData mThrottlingData; - private BrightnessThrottlingData mDdcThrottlingData; + + // Maps the throttling ID to the data. Sourced from DisplayDeviceConfig. + @NonNull + private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap; + + // Current throttling data being used. + // Null if we do not support throttling. + @Nullable + private ThermalBrightnessThrottlingData mThermalThrottlingData; + private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; private String mUniqueDisplayId; // The most recent string that has been set from DeviceConfig - private String mBrightnessThrottlingDataString; + private String mThermalBrightnessThrottlingDataString; + + // The brightness throttling configuration that should be used. + private String mThermalBrightnessThrottlingDataId; // This is a collection of brightness throttling data that has been written as overrides from // the DeviceConfig. This will always take priority over the display device config data. - private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride = - new HashMap<>(1); - - BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, - Runnable throttlingChangeCallback, String uniqueDisplayId) { - this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback, - uniqueDisplayId); + // We need to store the data for every display device, so we do not need to update this each + // time the underlying display device changes. + // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData. + // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >> + private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> + mThermalBrightnessThrottlingDataOverride = new HashMap<>(1); + + BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId, + String throttlingDataId, + @NonNull HashMap<String, ThermalBrightnessThrottlingData> + thermalBrightnessThrottlingDataMap) { + this(new Injector(), handler, handler, throttlingChangeCallback, + uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap); } @VisibleForTesting BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, - BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, - String uniqueDisplayId) { + Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId, + @NonNull HashMap<String, ThermalBrightnessThrottlingData> + thermalBrightnessThrottlingDataMap) { mInjector = injector; mHandler = handler; mDeviceConfigHandler = deviceConfigHandler; - mThrottlingData = throttlingData; - mDdcThrottlingData = throttlingData; + mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; mThrottlingChangeCallback = throttlingChangeCallback; mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mUniqueDisplayId = uniqueDisplayId; mDeviceConfig = injector.getDeviceConfig(); mDeviceConfigListener = new DeviceConfigListener(); - - resetThrottlingData(mThrottlingData, mUniqueDisplayId); + mThermalBrightnessThrottlingDataId = throttlingDataId; + mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; + loadThermalBrightnessThrottlingDataFromDeviceConfig(); + loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap, + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } boolean deviceSupportsThrottling() { - return mThrottlingData != null; + return mThermalThrottlingData != null; } float getBrightnessCap() { @@ -133,23 +156,14 @@ class BrightnessThrottler { mThrottlingStatus = THROTTLING_INVALID; } - private void resetThrottlingData() { - resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId); - } - - void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) { - stop(); - - mUniqueDisplayId = displayId; - mDdcThrottlingData = throttlingData; - mDeviceConfigListener.startListening(); - reloadBrightnessThrottlingDataOverride(); - mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId, - throttlingData); - - if (deviceSupportsThrottling()) { - mSkinThermalStatusObserver.startObserving(); - } + void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap, + String brightnessThrottlingDataId, + String uniqueDisplayId) { + mDdcThermalThrottlingDataMap = ddcThrottlingDataMap; + mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId; + mUniqueDisplayId = uniqueDisplayId; + resetThermalThrottlingData(); } private float verifyAndConstrainBrightnessCap(float brightness) { @@ -171,11 +185,11 @@ class BrightnessThrottler { private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) { if (mThrottlingStatus != newStatus) { mThrottlingStatus = newStatus; - updateThrottling(); + updateThermalThrottling(); } } - private void updateThrottling() { + private void updateThermalThrottling() { if (!deviceSupportsThrottling()) { return; } @@ -183,9 +197,9 @@ class BrightnessThrottler { float brightnessCap = PowerManager.BRIGHTNESS_MAX; int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; - if (mThrottlingStatus != THROTTLING_INVALID) { + if (mThrottlingStatus != THROTTLING_INVALID && mThermalThrottlingData != null) { // Throttling levels are sorted by increasing severity - for (ThrottlingLevel level : mThrottlingData.throttlingLevels) { + for (ThrottlingLevel level : mThermalThrottlingData.throttlingLevels) { if (level.thermalStatus <= mThrottlingStatus) { brightnessCap = level.brightness; brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; @@ -218,27 +232,40 @@ class BrightnessThrottler { private void dumpLocal(PrintWriter pw) { pw.println("BrightnessThrottler:"); - pw.println(" mThrottlingData=" + mThrottlingData); - pw.println(" mDdcThrottlingData=" + mDdcThrottlingData); + pw.println(" mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId); + pw.println(" mThermalThrottlingData=" + mThermalThrottlingData); pw.println(" mUniqueDisplayId=" + mUniqueDisplayId); pw.println(" mThrottlingStatus=" + mThrottlingStatus); pw.println(" mBrightnessCap=" + mBrightnessCap); pw.println(" mBrightnessMaxReason=" + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); - pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride); - pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString); + pw.println(" mDdcThermalThrottlingDataMap=" + mDdcThermalThrottlingDataMap); + pw.println(" mThermalBrightnessThrottlingDataOverride=" + + mThermalBrightnessThrottlingDataOverride); + pw.println(" mThermalBrightnessThrottlingDataString=" + + mThermalBrightnessThrottlingDataString); mSkinThermalStatusObserver.dump(pw); } - private String getBrightnessThrottlingDataString() { + private String getThermalBrightnessThrottlingDataString() { return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, /* defaultValue= */ null); } - private boolean parseAndSaveData(@NonNull String strArray, - @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) { + // The brightness throttling data id may or may not be specified in the string that is passed + // in, if there is none specified, we assume it is for the default case. Each string passed in + // here must be for one display and one throttling id. + // 123,1,critical,0.8 + // 456,2,moderate,0.9,critical,0.7 + // 456,2,moderate,0.9,critical,0.7,default + // 456,2,moderate,0.9,critical,0.7,id_2 + // displayId, number, <state, val> * number + // displayId, <number, <state, val> * number>, throttlingId + private boolean parseAndAddData(@NonNull String strArray, + @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> + displayIdToThrottlingIdToBtd) { boolean validConfig = true; String[] items = strArray.split(","); int i = 0; @@ -254,61 +281,110 @@ class BrightnessThrottler { for (int j = 0; j < noOfThrottlingPoints; j++) { String severity = items[i++]; int status = parseThermalStatus(severity); - float brightnessPoint = parseBrightness(items[i++]); - throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint)); } - BrightnessThrottlingData toSave = - DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels); - tempBrightnessThrottlingData.put(uniqueDisplayId, toSave); + + String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID; + ThermalBrightnessThrottlingData throttlingLevelsData = + DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels); + + // Add throttlingLevelsData to inner map where necessary. + HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay = + displayIdToThrottlingIdToBtd.get(uniqueDisplayId); + if (throttlingMapForDisplay == null) { + throttlingMapForDisplay = new HashMap<>(); + throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData); + displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay); + } else if (throttlingMapForDisplay.containsKey(throttlingDataId)) { + Slog.e(TAG, "Throttling data for display " + uniqueDisplayId + + "contains duplicate throttling ids: '" + throttlingDataId + "'"); + return false; + } else { + throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData); + } } catch (NumberFormatException | IndexOutOfBoundsException | UnknownThermalStatusException e) { - validConfig = false; Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e); + validConfig = false; } if (i != items.length) { validConfig = false; } - return validConfig; } - public void reloadBrightnessThrottlingDataOverride() { - HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData = + private void loadThermalBrightnessThrottlingDataFromDeviceConfig() { + HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData = new HashMap<>(1); - mBrightnessThrottlingDataString = getBrightnessThrottlingDataString(); + mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString(); boolean validConfig = true; - mBrightnessThrottlingDataOverride.clear(); - if (mBrightnessThrottlingDataString != null) { - String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";"); + mThermalBrightnessThrottlingDataOverride.clear(); + if (mThermalBrightnessThrottlingDataString != null) { + String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";"); for (String s : throttlingDataSplits) { - if (!parseAndSaveData(s, tempBrightnessThrottlingData)) { + if (!parseAndAddData(s, tempThrottlingData)) { validConfig = false; break; } } if (validConfig) { - mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData); - tempBrightnessThrottlingData.clear(); + mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData); + tempThrottlingData.clear(); } } else { - Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null"); + Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null"); } } + private void resetThermalThrottlingData() { + stop(); + + mDeviceConfigListener.startListening(); + + // Get throttling data for this id, if it exists + mThermalThrottlingData = getConfigFromId(mThermalBrightnessThrottlingDataId); + + // Fallback to default id otherwise. + if (!DEFAULT_ID.equals(mThermalBrightnessThrottlingDataId) + && mThermalThrottlingData == null) { + mThermalThrottlingData = getConfigFromId(DEFAULT_ID); + Slog.d(TAG, "Falling back to default throttling Id"); + } + + if (deviceSupportsThrottling()) { + mSkinThermalStatusObserver.startObserving(); + } + } + + private ThermalBrightnessThrottlingData getConfigFromId(String id) { + ThermalBrightnessThrottlingData returnValue; + + // Fallback pattern for fetching correct throttling data for this display and id. + // 1) throttling data from device config for this throttling data id + returnValue = mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null + ? null + : mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id); + // 2) throttling data from ddc for this throttling data id + returnValue = returnValue == null + ? mDdcThermalThrottlingDataMap.get(id) + : returnValue; + + return returnValue; + } + /** * Listens to config data change and updates the brightness throttling data using * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA. * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe, * 0.379518072;local:4619827677550801151,1,moderate,0.75" * In this order: - * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>] - * Where the latter part is repeated for each throttling level, and the entirety is repeated - * for each display, separated by a semicolon. + * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>][,throttlingId]? + * Where [<severity as string>,<brightness cap>] is repeated for each throttling level, and the + * entirety is repeated for each display & throttling data id, separated by a semicolon. */ public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); @@ -320,8 +396,8 @@ class BrightnessThrottler { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { - reloadBrightnessThrottlingDataOverride(); - resetThrottlingData(); + loadThermalBrightnessThrottlingDataFromDeviceConfig(); + resetThermalThrottlingData(); } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index f4b3f1ab7b11..a021174e3031 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -143,17 +143,17 @@ import javax.xml.datatype.DatatypeConfigurationException; * <brightness>0.01</brightness> * </brightnessThrottlingPoint> * </brightnessThrottlingMap> - * <concurrentDisplaysBrightnessThrottlingMap> - * <brightnessThrottlingPoint> - * <thermalStatus>severe</thermalStatus> - * <brightness>0.07</brightness> - * </brightnessThrottlingPoint> - * <brightnessThrottlingPoint> - * <thermalStatus>critical</thermalStatus> - * <brightness>0.005</brightness> - * </brightnessThrottlingPoint> - * </concurrentDisplaysBrightnessThrottlingMap> - * <refreshRateThrottlingMap> + * <brightnessThrottlingMap id="id_2"> // optional attribute, leave blank for default + * <brightnessThrottlingPoint> + * <thermalStatus>moderate</thermalStatus> + * <brightness>0.2</brightness> + * </brightnessThrottlingPoint> + * <brightnessThrottlingPoint> + * <thermalStatus>severe</thermalStatus> + * <brightness>0.1</brightness> + * </brightnessThrottlingPoint> + * </brightnessThrottlingMap> + <refreshRateThrottlingMap> * <refreshRateThrottlingPoint> * <thermalStatus>critical</thermalStatus> * <refreshRateRange> @@ -687,8 +687,8 @@ public class DisplayDeviceConfig { private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; - private final Map<String, BrightnessThrottlingData> mBrightnessThrottlingDataMap = - new HashMap<>(); + private final HashMap<String, ThermalBrightnessThrottlingData> + mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>(); private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>> mRefreshRateThrottlingMap = new HashMap<>(); @@ -1346,11 +1346,11 @@ public class DisplayDeviceConfig { } /** - * @param id The ID of the throttling data - * @return brightness throttling configuration data for the display. + * @return brightness throttling configuration data for this display, for each throttling id. */ - public BrightnessThrottlingData getBrightnessThrottlingData(String id) { - return BrightnessThrottlingData.create(mBrightnessThrottlingDataMap.get(id)); + public HashMap<String, ThermalBrightnessThrottlingData> + getThermalBrightnessThrottlingDataMapByThrottlingId() { + return mThermalBrightnessThrottlingDataMapByThrottlingId; } /** @@ -1358,7 +1358,7 @@ public class DisplayDeviceConfig { * @return refresh rate throttling configuration */ @Nullable - public SparseArray<SurfaceControl.RefreshRateRange> getRefreshRateThrottlingData( + public SparseArray<SurfaceControl.RefreshRateRange> getThermalRefreshRateThrottlingData( @Nullable String id) { String key = id == null ? DEFAULT_ID : id; return mRefreshRateThrottlingMap.get(key); @@ -1525,7 +1525,8 @@ public class DisplayDeviceConfig { + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline - + ", mBrightnessThrottlingData=" + mBrightnessThrottlingDataMap + + ", mThermalBrightnessThrottlingDataMapByThrottlingId=" + + mThermalBrightnessThrottlingDataMapByThrottlingId + "\n" + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease @@ -1886,11 +1887,11 @@ public class DisplayDeviceConfig { Slog.i(TAG, "No thermal throttling config found"); return; } - loadBrightnessThrottlingMaps(throttlingConfig); - loadRefreshRateThermalThrottlingMap(throttlingConfig); + loadThermalBrightnessThrottlingMaps(throttlingConfig); + loadThermalRefreshRateThrottlingMap(throttlingConfig); } - private void loadBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) { + private void loadThermalBrightnessThrottlingMaps(ThermalThrottling throttlingConfig) { final List<BrightnessThrottlingMap> maps = throttlingConfig.getBrightnessThrottlingMap(); if (maps == null || maps.isEmpty()) { Slog.i(TAG, "No brightness throttling map found"); @@ -1900,7 +1901,7 @@ public class DisplayDeviceConfig { for (BrightnessThrottlingMap map : maps) { final List<BrightnessThrottlingPoint> points = map.getBrightnessThrottlingPoint(); // At least 1 point is guaranteed by the display device config schema - List<BrightnessThrottlingData.ThrottlingLevel> throttlingLevels = + List<ThermalBrightnessThrottlingData.ThrottlingLevel> throttlingLevels = new ArrayList<>(points.size()); boolean badConfig = false; @@ -1911,24 +1912,24 @@ public class DisplayDeviceConfig { break; } - throttlingLevels.add(new BrightnessThrottlingData.ThrottlingLevel( + throttlingLevels.add(new ThermalBrightnessThrottlingData.ThrottlingLevel( convertThermalStatus(status), point.getBrightness().floatValue())); } if (!badConfig) { String id = map.getId() == null ? DEFAULT_ID : map.getId(); - if (mBrightnessThrottlingDataMap.containsKey(id)) { + if (mThermalBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) { throw new RuntimeException("Brightness throttling data with ID " + id + " already exists"); } - mBrightnessThrottlingDataMap.put(id, - BrightnessThrottlingData.create(throttlingLevels)); + mThermalBrightnessThrottlingDataMapByThrottlingId.put(id, + ThermalBrightnessThrottlingData.create(throttlingLevels)); } } } - private void loadRefreshRateThermalThrottlingMap(ThermalThrottling throttlingConfig) { + private void loadThermalRefreshRateThrottlingMap(ThermalThrottling throttlingConfig) { List<RefreshRateThrottlingMap> maps = throttlingConfig.getRefreshRateThrottlingMap(); if (maps == null || maps.isEmpty()) { Slog.w(TAG, "RefreshRateThrottling: map not found"); @@ -1971,8 +1972,8 @@ public class DisplayDeviceConfig { )); } if (refreshRates.size() == 0) { - Slog.w(TAG, "RefreshRateThrottling: no valid throttling points fond for map, mapId=" - + id); + Slog.w(TAG, "RefreshRateThrottling: no valid throttling points found for map, " + + "mapId=" + id); continue; } mRefreshRateThrottlingMap.put(id, refreshRates); @@ -3038,7 +3039,7 @@ public class DisplayDeviceConfig { /** * Container for brightness throttling data. */ - public static class BrightnessThrottlingData { + public static class ThermalBrightnessThrottlingData { public List<ThrottlingLevel> throttlingLevels; static class ThrottlingLevel { @@ -3077,9 +3078,10 @@ public class DisplayDeviceConfig { /** - * Creates multiple teperature based throttling levels of brightness + * Creates multiple temperature based throttling levels of brightness */ - public static BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels) { + public static ThermalBrightnessThrottlingData create( + List<ThrottlingLevel> throttlingLevels) { if (throttlingLevels == null || throttlingLevels.size() == 0) { Slog.e(TAG, "BrightnessThrottlingData received null or empty throttling levels"); return null; @@ -3117,21 +3119,12 @@ public class DisplayDeviceConfig { } } - return new BrightnessThrottlingData(throttlingLevels); + return new ThermalBrightnessThrottlingData(throttlingLevels); } - static public BrightnessThrottlingData create(BrightnessThrottlingData other) { - if (other == null) { - return null; - } - - return BrightnessThrottlingData.create(other.throttlingLevels); - } - - @Override public String toString() { - return "BrightnessThrottlingData{" + return "ThermalBrightnessThrottlingData{" + "throttlingLevels:" + throttlingLevels + "} "; } @@ -3142,12 +3135,12 @@ public class DisplayDeviceConfig { return true; } - if (!(obj instanceof BrightnessThrottlingData)) { + if (!(obj instanceof ThermalBrightnessThrottlingData)) { return false; } - BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj; - return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels); + ThermalBrightnessThrottlingData otherData = (ThermalBrightnessThrottlingData) obj; + return throttlingLevels.equals(otherData.throttlingLevels); } @Override @@ -3156,7 +3149,7 @@ public class DisplayDeviceConfig { } @VisibleForTesting - BrightnessThrottlingData(List<ThrottlingLevel> inLevels) { + ThermalBrightnessThrottlingData(List<ThrottlingLevel> inLevels) { throttlingLevels = new ArrayList<>(inLevels.size()); for (ThrottlingLevel level : inLevels) { throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness)); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index f5859eed34f1..5e3990ac7167 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -505,7 +505,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private DisplayDeviceConfig mDisplayDeviceConfig; - // Identifiers for suspend blocker acuisition requests + // Identifiers for suspend blocker acquisition requests private final String mSuspendBlockerIdUnfinishedBusiness; private final String mSuspendBlockerIdOnStateChanged; private final String mSuspendBlockerIdProxPositive; @@ -515,7 +515,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mIsEnabled; private boolean mIsInTransition; - private String mBrightnessThrottlingDataId; + // The id of the thermal brightness throttling policy that should be used. + private String mThermalBrightnessThrottlingDataId; // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there // is one lead display, the additional displays follow the brightness value of the lead display. @@ -555,7 +556,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler = new DisplayControllerHandler(handler.getLooper()); mLastBrightnessEvent = new BrightnessEvent(mDisplayId); mTempBrightnessEvent = new BrightnessEvent(mDisplayId); - mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked(); + mThermalBrightnessThrottlingDataId = + logicalDisplay.getThermalBrightnessThrottlingDataIdLocked(); if (mDisplayId == Display.DEFAULT_DISPLAY) { mBatteryStats = BatteryStatsService.getService(); @@ -890,8 +892,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); - final String brightnessThrottlingDataId = - mLogicalDisplay.getBrightnessThrottlingDataIdLocked(); + final String thermalBrightnessThrottlingDataId = + mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(); mHandler.postAtTime(() -> { boolean changed = false; if (mDisplayDevice != device) { @@ -900,19 +902,21 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; loadFromDisplayDeviceConfig(token, info, hbmMetadata); loadNitBasedBrightnessSetting(); /// Since the underlying display-device changed, we really don't know the - // last command that was sent to change it's state. Lets assume it is unknown so + // last command that was sent to change it's state. Let's assume it is unknown so // that we trigger a change immediately. mPowerState.resetScreenState(); - } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) { + } else if ( + !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) { changed = true; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; - mBrightnessThrottler.resetThrottlingData( - config.getBrightnessThrottlingData(mBrightnessThrottlingDataId), + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + config.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { @@ -981,9 +985,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sdrBrightness, maxDesiredHdrSdrRatio); } }); - mBrightnessThrottler.resetThrottlingData( - mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId), - mUniqueDisplayId); + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } private void sendUpdatePowerState() { @@ -2116,11 +2120,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); return new BrightnessThrottler(mHandler, - ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId), () -> { sendUpdatePowerState(); postBrightnessChangeRunnable(); - }, mUniqueDisplayId); + }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(), + ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()); } private void blockScreenOn() { diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 8ce4b66eba28..23e606c028ac 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -400,7 +400,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private boolean mIsEnabled; private boolean mIsInTransition; - private String mBrightnessThrottlingDataId; + // The id of the thermal brightness throttling policy that should be used. + private String mThermalBrightnessThrottlingDataId; // DPCs following the brightness of this DPC. This is used in concurrent displays mode - there // is one lead display, the additional displays follow the brightness value of the lead display. @@ -438,7 +439,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId); mTag = "DisplayPowerController2[" + mDisplayId + "]"; - mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked(); + mThermalBrightnessThrottlingDataId = + logicalDisplay.getThermalBrightnessThrottlingDataIdLocked(); mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); @@ -706,8 +708,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); final boolean isEnabled = mLogicalDisplay.isEnabledLocked(); final boolean isInTransition = mLogicalDisplay.isInTransitionLocked(); - final String brightnessThrottlingDataId = - mLogicalDisplay.getBrightnessThrottlingDataIdLocked(); + final String thermalBrightnessThrottlingDataId = + mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(); mHandler.postAtTime(() -> { boolean changed = false; @@ -717,19 +719,21 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; loadFromDisplayDeviceConfig(token, info, hbmMetadata); mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config); // Since the underlying display-device changed, we really don't know the - // last command that was sent to change it's state. Lets assume it is unknown so + // last command that was sent to change it's state. Let's assume it is unknown so // that we trigger a change immediately. mPowerState.resetScreenState(); - } else if (!mBrightnessThrottlingDataId.equals(brightnessThrottlingDataId)) { + } else if ( + !mThermalBrightnessThrottlingDataId.equals(thermalBrightnessThrottlingDataId)) { changed = true; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; - mBrightnessThrottler.resetThrottlingData( - config.getBrightnessThrottlingData(mBrightnessThrottlingDataId), + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + config.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { @@ -795,9 +799,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal sdrBrightness, maxDesiredHdrSdrRatio); } }); - mBrightnessThrottler.resetThrottlingData( - mDisplayDeviceConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId), - mUniqueDisplayId); + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } private void sendUpdatePowerState() { @@ -1757,11 +1761,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig(); return new BrightnessThrottler(mHandler, - ddConfig.getBrightnessThrottlingData(mBrightnessThrottlingDataId), () -> { sendUpdatePowerState(); postBrightnessChangeRunnable(); - }, mUniqueDisplayId); + }, mUniqueDisplayId, mLogicalDisplay.getThermalBrightnessThrottlingDataIdLocked(), + ddConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()); } private void blockScreenOn() { diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index dee4cdea65fe..dab00d8070d4 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -175,11 +175,11 @@ final class LogicalDisplay { private boolean mDirty = false; /** - * The ID of the brightness throttling data that should be used. This can change e.g. in - * concurrent displays mode in which a stricter brightness throttling policy might need to be - * used. + * The ID of the thermal brightness throttling data that should be used. This can change e.g. + * in concurrent displays mode in which a stricter brightness throttling policy might need to + * be used. */ - private String mBrightnessThrottlingDataId; + private String mThermalBrightnessThrottlingDataId; public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) { mDisplayId = displayId; @@ -189,7 +189,7 @@ final class LogicalDisplay { mTempFrameRateOverride = new SparseArray<>(); mIsEnabled = true; mIsInTransition = false; - mBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; + mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; } public void setDevicePositionLocked(int position) { @@ -349,7 +349,7 @@ final class LogicalDisplay { * * @param refreshRanges new refreshRateThermalThrottling ranges limited by layout or default */ - public void updateRefreshRateThermalThrottling( + public void updateThermalRefreshRateThrottling( @Nullable SparseArray<SurfaceControl.RefreshRateRange> refreshRanges) { if (refreshRanges == null) { refreshRanges = new SparseArray<>(); @@ -872,16 +872,16 @@ final class LogicalDisplay { /** * @return The ID of the brightness throttling data that this display should use. */ - public String getBrightnessThrottlingDataIdLocked() { - return mBrightnessThrottlingDataId; + public String getThermalBrightnessThrottlingDataIdLocked() { + return mThermalBrightnessThrottlingDataId; } /** * @param brightnessThrottlingDataId The ID of the brightness throttling data that this * display should use. */ - public void setBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) { - mBrightnessThrottlingDataId = + public void setThermalBrightnessThrottlingDataIdLocked(String brightnessThrottlingDataId) { + mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId; } @@ -950,7 +950,7 @@ final class LogicalDisplay { pw.println("mFrameRateOverrides=" + Arrays.toString(mFrameRateOverrides)); pw.println("mPendingFrameRateOverrideUids=" + mPendingFrameRateOverrideUids); pw.println("mDisplayGroupName=" + mDisplayGroupName); - pw.println("mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId); + pw.println("mThermalBrightnessThrottlingDataId=" + mThermalBrightnessThrottlingDataId); pw.println("mLeadDisplayId=" + mLeadDisplayId); } diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 424eedc876ec..254441c2aa13 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1022,17 +1022,17 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { newDisplay.updateLayoutLimitedRefreshRateLocked( config.getRefreshRange(displayLayout.getRefreshRateZoneId()) ); - newDisplay.updateRefreshRateThermalThrottling( - config.getRefreshRateThrottlingData( + newDisplay.updateThermalRefreshRateThrottling( + config.getThermalRefreshRateThrottlingData( displayLayout.getRefreshRateThermalThrottlingMapId() ) ); setEnabledLocked(newDisplay, displayLayout.isEnabled()); - newDisplay.setBrightnessThrottlingDataIdLocked( - displayLayout.getBrightnessThrottlingMapId() == null + newDisplay.setThermalBrightnessThrottlingDataIdLocked( + displayLayout.getThermalBrightnessThrottlingMapId() == null ? DisplayDeviceConfig.DEFAULT_ID - : displayLayout.getBrightnessThrottlingMapId()); + : displayLayout.getThermalBrightnessThrottlingMapId()); newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName()); } diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index f86ee249408c..b55d7d5d9d3c 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -234,11 +234,11 @@ public class Layout { // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified. private final int mPosition; - // The ID of the brightness throttling map that should be used. This can change e.g. in - // concurrent displays mode in which a stricter brightness throttling policy might need to - // be used. + // The ID of the thermal brightness throttling map that should be used. This can change + // e.g. in concurrent displays mode in which a stricter brightness throttling policy might + // need to be used. @Nullable - private final String mBrightnessThrottlingMapId; + private final String mThermalBrightnessThrottlingMapId; // The ID of the lead display that this display will follow in a layout. -1 means no lead. private final int mLeadDisplayId; @@ -248,7 +248,7 @@ public class Layout { private final String mRefreshRateZoneId; @Nullable - private final String mRefreshRateThermalThrottlingMapId; + private final String mThermalRefreshRateThrottlingMapId; private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, @@ -259,9 +259,9 @@ public class Layout { mIsEnabled = isEnabled; mDisplayGroupName = displayGroupName; mPosition = position; - mBrightnessThrottlingMapId = brightnessThrottlingMapId; + mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId; mRefreshRateZoneId = refreshRateZoneId; - mRefreshRateThermalThrottlingMapId = refreshRateThermalThrottlingMapId; + mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; mLeadDisplayId = leadDisplayId; } @@ -273,10 +273,10 @@ public class Layout { + ", displayGroupName: " + mDisplayGroupName + ", addr: " + mAddress + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) - + ", brightnessThrottlingMapId: " + mBrightnessThrottlingMapId + + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId + ", mRefreshRateZoneId: " + mRefreshRateZoneId + ", mLeadDisplayId: " + mLeadDisplayId - + ", mRefreshRateThermalThrottlingMapId: " + mRefreshRateThermalThrottlingMapId + + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId + "}"; } @@ -293,12 +293,12 @@ public class Layout { && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId && this.mDisplayGroupName.equals(otherDisplay.mDisplayGroupName) && this.mAddress.equals(otherDisplay.mAddress) - && Objects.equals(mBrightnessThrottlingMapId, - otherDisplay.mBrightnessThrottlingMapId) + && Objects.equals(mThermalBrightnessThrottlingMapId, + otherDisplay.mThermalBrightnessThrottlingMapId) && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId) && this.mLeadDisplayId == otherDisplay.mLeadDisplayId - && Objects.equals(mRefreshRateThermalThrottlingMapId, - otherDisplay.mRefreshRateThermalThrottlingMapId); + && Objects.equals(mThermalRefreshRateThrottlingMapId, + otherDisplay.mThermalRefreshRateThrottlingMapId); } @Override @@ -309,10 +309,10 @@ public class Layout { result = 31 * result + mLogicalDisplayId; result = 31 * result + mDisplayGroupName.hashCode(); result = 31 * result + mAddress.hashCode(); - result = 31 * result + mBrightnessThrottlingMapId.hashCode(); + result = 31 * result + mThermalBrightnessThrottlingMapId.hashCode(); result = 31 * result + Objects.hashCode(mRefreshRateZoneId); result = 31 * result + mLeadDisplayId; - result = 31 * result + Objects.hashCode(mRefreshRateThermalThrottlingMapId); + result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); return result; } @@ -338,10 +338,12 @@ public class Layout { } /** - * @return The ID of the brightness throttling map that this display should use. + * Gets the id of the thermal brightness throttling map that should be used. + * @return The ID of the thermal brightness throttling map that this display should use, + * null if unspecified, will fall back to default. */ - public String getBrightnessThrottlingMapId() { - return mBrightnessThrottlingMapId; + public String getThermalBrightnessThrottlingMapId() { + return mThermalBrightnessThrottlingMapId; } /** @@ -359,7 +361,7 @@ public class Layout { } public String getRefreshRateThermalThrottlingMapId() { - return mRefreshRateThermalThrottlingMapId; + return mThermalRefreshRateThrottlingMapId; } } } diff --git a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java index f93d9ee55d30..c04735d8f7e2 100644 --- a/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java +++ b/services/core/java/com/android/server/display/mode/SkinThermalStatusObserver.java @@ -102,7 +102,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme //region DisplayManager.DisplayListener @Override public void onDisplayAdded(int displayId) { - updateRefreshRateThermalThrottling(displayId); + updateThermalRefreshRateThrottling(displayId); if (mLoggingEnabled) { Slog.d(TAG, "Display added:" + displayId); } @@ -122,7 +122,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme @Override public void onDisplayChanged(int displayId) { - updateRefreshRateThermalThrottling(displayId); + updateThermalRefreshRateThrottling(displayId); if (mLoggingEnabled) { Slog.d(TAG, "Display changed:" + displayId); } @@ -150,7 +150,7 @@ final class SkinThermalStatusObserver extends IThermalEventListener.Stub impleme } } - private void updateRefreshRateThermalThrottling(int displayId) { + private void updateThermalRefreshRateThrottling(int displayId) { DisplayInfo displayInfo = new DisplayInfo(); mInjector.getDisplayInfo(displayId, displayInfo); SparseArray<SurfaceControl.RefreshRateRange> throttlingMap = diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index d3dea0d96812..b8900d7acee5 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -16,7 +16,6 @@ package com.android.server.notification; import static android.app.Notification.FLAG_BUBBLE; -import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.NotificationChannel.ALLOW_BUBBLE_OFF; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; @@ -81,7 +80,7 @@ public class BubbleExtractor implements NotificationSignalExtractor { && !mActivityManager.isLowRamDevice() && record.isConversation() && record.getShortcutInfo() != null - && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0; + && !record.getNotification().isFgsOrUij(); boolean userEnabledBubbles = mConfig.bubblesEnabled(record.getUser()); int appPreference = diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 6f0903cf8685..446c4f7e335a 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -165,7 +165,7 @@ public class NotificationComparator if (isCallStyle(record)) { return true; } - if (!isOngoing(record)) { + if (!record.getNotification().isFgsOrUij()) { return false; } return isCallCategory(record) || isMediaNotification(record); @@ -199,11 +199,6 @@ public class NotificationComparator return false; } - private boolean isOngoing(NotificationRecord record) { - final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE; - return (record.getNotification().flags & ongoingFlags) != 0; - } - private boolean isMediaNotification(NotificationRecord record) { return record.getNotification().isMediaNotification(); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index bc3885605a6c..919fc712c409 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -35,6 +35,8 @@ public interface NotificationManagerInternal { void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId); + void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId, int userId); + void onConversationRemoved(String pkg, int uid, Set<String> shortcuts); /** Get the number of notification channels for a given package */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 81cca50a4d29..6d27fe058423 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -21,6 +21,7 @@ import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_ import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; +import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED; @@ -29,6 +30,7 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; @@ -163,6 +165,7 @@ import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.RemoteServiceException.BadForegroundServiceNotificationException; +import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException; import android.app.StatsManager; import android.app.StatusBarManager; import android.app.UriGrantsManager; @@ -304,6 +307,7 @@ import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.job.JobSchedulerInternal; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.notification.ManagedServices.ManagedServiceInfo; @@ -1156,8 +1160,8 @@ public class NotificationManagerService extends SystemService { StatusBarNotification sbn = r.getSbn(); cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(), sbn.getId(), Notification.FLAG_AUTO_CANCEL, - FLAG_FOREGROUND_SERVICE | FLAG_BUBBLE, false, r.getUserId(), - REASON_CLICK, nv.rank, nv.count, null); + FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_BUBBLE, + false, r.getUserId(), REASON_CLICK, nv.rank, nv.count, null); nv.recycle(); reportUserInteraction(r); mAssistants.notifyAssistantNotificationClicked(r); @@ -1265,21 +1269,26 @@ public class NotificationManagerService extends SystemService { public void onNotificationError(int callingUid, int callingPid, String pkg, String tag, int id, int uid, int initialPid, String message, int userId) { final boolean fgService; + final boolean uiJob; synchronized (mNotificationLock) { NotificationRecord r = findNotificationLocked(pkg, tag, id, userId); fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0; + uiJob = r != null && (r.getNotification().flags & FLAG_USER_INITIATED_JOB) != 0; } cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId, REASON_ERROR, null); - if (fgService) { - // Still crash for foreground services, preventing the not-crash behaviour abused - // by apps to give us a garbage notification and silently start a fg service. + if (fgService || uiJob) { + // Still crash for foreground services or user-initiated jobs, preventing the + // not-crash behaviour abused by apps to give us a garbage notification and + // silently start a fg service or user-initiated job. + final int exceptionTypeId = fgService + ? BadForegroundServiceNotificationException.TYPE_ID + : BadUserInitiatedJobNotificationException.TYPE_ID; Binder.withCleanCallingIdentity( () -> mAm.crashApplicationWithType(uid, initialPid, pkg, -1, "Bad notification(tag=" + tag + ", id=" + id + ") posted from package " + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): " - + message, true /* force */, - BadForegroundServiceNotificationException.TYPE_ID)); + + message, true /* force */, exceptionTypeId)); } } @@ -1689,8 +1698,8 @@ public class NotificationManagerService extends SystemService { cancelNotification(record.getSbn().getUid(), record.getSbn().getInitialPid(), record.getSbn().getPackageName(), record.getSbn().getTag(), record.getSbn().getId(), 0, - FLAG_FOREGROUND_SERVICE, true, record.getUserId(), - REASON_TIMEOUT, null); + FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, + true, record.getUserId(), REASON_TIMEOUT, null); } } } @@ -3519,10 +3528,10 @@ public class NotificationManagerService extends SystemService { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg); - // Don't allow the app to cancel active FGS notifications + // Don't allow the app to cancel active FGS or UIJ notifications cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(), - pkg, null, 0, FLAG_FOREGROUND_SERVICE, true, userId, - REASON_APP_CANCEL_ALL, null); + pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB, + true, userId, REASON_APP_CANCEL_ALL, null); } @Override @@ -3964,6 +3973,21 @@ public class NotificationManagerService extends SystemService { } } + // Throws a security exception if the given channel has a notification associated + // with an active user-initiated job. + private void enforceDeletingChannelHasNoUserInitiatedJob(String pkg, int userId, + String channelId) { + final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); + if (js != null && js.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + channelId, userId, pkg)) { + Slog.w(TAG, "Package u" + userId + "/" + pkg + + " may not delete notification channel '" + + channelId + "' with user-initiated job"); + throw new SecurityException("Not allowed to delete channel " + channelId + + " with a user-initiated job"); + } + } + @Override public void deleteNotificationChannel(String pkg, String channelId) { checkCallerIsSystemOrSameApp(pkg); @@ -3973,6 +3997,7 @@ public class NotificationManagerService extends SystemService { throw new IllegalArgumentException("Cannot delete default channel"); } enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId); + enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, callingUser, REASON_CHANNEL_REMOVED, null); boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel( @@ -4017,8 +4042,9 @@ public class NotificationManagerService extends SystemService { final int userId = UserHandle.getUserId(callingUid); List<NotificationChannel> groupChannels = groupToDelete.getChannels(); for (int i = 0; i < groupChannels.size(); i++) { - enforceDeletingChannelHasNoFgService(pkg, userId, - groupChannels.get(i).getId()); + final String channelId = groupChannels.get(i).getId(); + enforceDeletingChannelHasNoFgService(pkg, userId, channelId); + enforceDeletingChannelHasNoUserInitiatedJob(pkg, userId, channelId); } List<NotificationChannel> deletedChannels = mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId); @@ -6360,61 +6386,73 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystem(); mHandler.post(() -> { synchronized (mNotificationLock) { - int count = getNotificationCount(pkg, userId); - boolean removeFgsNotification = false; - if (count > MAX_PACKAGE_NOTIFICATIONS) { - mUsageStats.registerOverCountQuota(pkg); - removeFgsNotification = true; - } - if (removeFgsNotification) { - NotificationRecord r = findNotificationLocked(pkg, null, notificationId, - userId); - if (r != null) { - if (DBG) { - Slog.d(TAG, "Remove FGS flag not allow. Cancel FGS notification"); - } - removeFromNotificationListsLocked(r); - cancelNotificationLocked(r, false, REASON_APP_CANCEL, true, - null, SystemClock.elapsedRealtime()); - } - } else { - // strip flag from all enqueued notifications. listeners will be informed - // in post runnable. - List<NotificationRecord> enqueued = findNotificationsByListLocked( - mEnqueuedNotifications, pkg, null, notificationId, userId); - for (int i = 0; i < enqueued.size(); i++) { - removeForegroundServiceFlagLocked(enqueued.get(i)); - } - - // if posted notification exists, strip its flag and tell listeners - NotificationRecord r = findNotificationByListLocked( - mNotificationList, pkg, null, notificationId, userId); - if (r != null) { - removeForegroundServiceFlagLocked(r); - mRankingHelper.sort(mNotificationList); - mListeners.notifyPostedLocked(r, r); - } - } + removeFlagFromNotificationLocked(pkg, notificationId, userId, + FLAG_FOREGROUND_SERVICE); } }); } @Override - public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) { - onConversationRemovedInternal(pkg, uid, shortcuts); + public void removeUserInitiatedJobFlagFromNotification(String pkg, int notificationId, + int userId) { + checkCallerIsSystem(); + mHandler.post(() -> { + synchronized (mNotificationLock) { + removeFlagFromNotificationLocked(pkg, notificationId, userId, + FLAG_USER_INITIATED_JOB); + } + }); } @GuardedBy("mNotificationLock") - private void removeForegroundServiceFlagLocked(NotificationRecord r) { - if (r == null) { - return; + private void removeFlagFromNotificationLocked(String pkg, int notificationId, int userId, + int flag) { + int count = getNotificationCount(pkg, userId); + boolean removeFlagFromNotification = false; + if (count > MAX_PACKAGE_NOTIFICATIONS) { + mUsageStats.registerOverCountQuota(pkg); + removeFlagFromNotification = true; + } + if (removeFlagFromNotification) { + NotificationRecord r = findNotificationLocked(pkg, null, notificationId, userId); + if (r != null) { + if (DBG) { + final String type = (flag == FLAG_FOREGROUND_SERVICE) ? "FGS" : "UIJ"; + Slog.d(TAG, "Remove " + type + " flag not allow. " + + "Cancel " + type + " notification"); + } + removeFromNotificationListsLocked(r); + cancelNotificationLocked(r, false, REASON_APP_CANCEL, true, + null, SystemClock.elapsedRealtime()); + } + } else { + List<NotificationRecord> enqueued = findNotificationsByListLocked( + mEnqueuedNotifications, pkg, null, notificationId, userId); + for (int i = 0; i < enqueued.size(); i++) { + final NotificationRecord r = enqueued.get(i); + if (r != null) { + // strip flag from all enqueued notifications. listeners will be informed + // in post runnable. + StatusBarNotification sbn = r.getSbn(); + sbn.getNotification().flags = (r.mOriginalFlags & ~flag); + } + } + + NotificationRecord r = findNotificationByListLocked( + mNotificationList, pkg, null, notificationId, userId); + if (r != null) { + // if posted notification exists, strip its flag and tell listeners + StatusBarNotification sbn = r.getSbn(); + sbn.getNotification().flags = (r.mOriginalFlags & ~flag); + mRankingHelper.sort(mNotificationList); + mListeners.notifyPostedLocked(r, r); + } } - StatusBarNotification sbn = r.getSbn(); - // NoMan adds flags FLAG_ONGOING_EVENT when it sees - // FLAG_FOREGROUND_SERVICE. Hence it's not enough to remove - // FLAG_FOREGROUND_SERVICE, we have to revert to the flags we received - // initially *and* force remove FLAG_FOREGROUND_SERVICE. - sbn.getNotification().flags = (r.mOriginalFlags & ~FLAG_FOREGROUND_SERVICE); + } + + @Override + public void onConversationRemoved(String pkg, int uid, Set<String> shortcuts) { + onConversationRemovedInternal(pkg, uid, shortcuts); } @Override @@ -6490,10 +6528,10 @@ public class NotificationManagerService extends SystemService { } } - // Don't allow client applications to cancel foreground service notifs or autobundled - // summaries. + // Don't allow client applications to cancel foreground service notifs, user-initiated job + // notifs or autobundled summaries. final int mustNotHaveFlags = isCallingUidSystem() ? 0 : - (FLAG_FOREGROUND_SERVICE | FLAG_AUTOGROUP_SUMMARY); + (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY); cancelNotification(uid, callingPid, pkg, tag, id, 0, mustNotHaveFlags, false, userId, REASON_APP_CANCEL, null); } @@ -6547,9 +6585,16 @@ public class NotificationManagerService extends SystemService { final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification( notification, tag, id, pkg, userId); + boolean stripUijFlag = true; + final JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class); + if (js != null) { + stripUijFlag = !js.isNotificationAssociatedWithAnyUserInitiatedJobs(id, userId, pkg); + } + // Fix the notification as best we can. try { - fixNotification(notification, pkg, tag, id, userId, notificationUid, policy); + fixNotification(notification, pkg, tag, id, userId, notificationUid, + policy, stripUijFlag); } catch (Exception e) { if (notification.isForegroundService()) { throw new SecurityException("Invalid FGS notification", e); @@ -6558,7 +6603,6 @@ public class NotificationManagerService extends SystemService { return; } - if (policy == ServiceNotificationPolicy.UPDATE_ONLY) { // Proceed if the notification is already showing/known, otherwise ignore // because the service lifecycle logic has retained responsibility for its @@ -6617,31 +6661,25 @@ public class NotificationManagerService extends SystemService { boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId); r.setImportanceFixed(isImportanceFixed); - if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { - final boolean fgServiceShown = channel.isFgServiceShown(); + if (notification.isFgsOrUij()) { if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0 - || !fgServiceShown) + || !channel.isUserVisibleTaskShown()) && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) { - // Increase the importance of foreground service notifications unless the user had - // an opinion otherwise (and the channel hasn't yet shown a fg service). - if (TextUtils.isEmpty(channelId) - || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { - r.setSystemImportance(IMPORTANCE_LOW); - } else { - channel.setImportance(IMPORTANCE_LOW); - r.setSystemImportance(IMPORTANCE_LOW); - if (!fgServiceShown) { - channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - channel.setFgServiceShown(true); - } - mPreferencesHelper.updateNotificationChannel( - pkg, notificationUid, channel, false); - r.updateNotificationChannel(channel); - } - } else if (!fgServiceShown && !TextUtils.isEmpty(channelId) + // Increase the importance of fgs/uij notifications unless the user had + // an opinion otherwise (and the channel hasn't yet shown a fgs/uij). + channel.setImportance(IMPORTANCE_LOW); + r.setSystemImportance(IMPORTANCE_LOW); + if (!channel.isUserVisibleTaskShown()) { + channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); + channel.setUserVisibleTaskShown(true); + } + mPreferencesHelper.updateNotificationChannel( + pkg, notificationUid, channel, false); + r.updateNotificationChannel(channel); + } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId) && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { - channel.setFgServiceShown(true); + channel.setUserVisibleTaskShown(true); r.updateNotificationChannel(channel); } } @@ -6734,7 +6772,8 @@ public class NotificationManagerService extends SystemService { @VisibleForTesting protected void fixNotification(Notification notification, String pkg, String tag, int id, - @UserIdInt int userId, int notificationUid, ServiceNotificationPolicy fgsPolicy) + @UserIdInt int userId, int notificationUid, + ServiceNotificationPolicy fgsPolicy, boolean stripUijFlag) throws NameNotFoundException, RemoteException { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, @@ -6744,6 +6783,14 @@ public class NotificationManagerService extends SystemService { if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) { notification.flags &= ~FLAG_FOREGROUND_SERVICE; } + if (notification.isUserInitiatedJob() && stripUijFlag) { + notification.flags &= ~FLAG_USER_INITIATED_JOB; + } + + // Remove FLAG_AUTO_CANCEL from notifications that are associated with a FGS or UIJ. + if (notification.isFgsOrUij()) { + notification.flags &= ~FLAG_AUTO_CANCEL; + } // Only notifications that can be non-dismissible can have the flag FLAG_NO_DISMISS if (mFlagResolver.isEnabled(ALLOW_DISMISS_ONGOING)) { @@ -7088,8 +7135,8 @@ public class NotificationManagerService extends SystemService { } } - // limit the number of non-fgs outstanding notificationrecords an app can have - if (!n.isForegroundService()) { + // limit the number of non-fgs/uij outstanding notificationrecords an app can have + if (!n.isFgsOrUij()) { int count = getNotificationCount(pkg, userId, id, tag); if (count >= MAX_PACKAGE_NOTIFICATIONS) { mUsageStats.registerOverCountQuota(pkg); @@ -7449,7 +7496,8 @@ public class NotificationManagerService extends SystemService { return false; } } else if (mReason == REASON_APP_CANCEL) { - if ((flags & FLAG_FOREGROUND_SERVICE) != 0) { + if ((flags & FLAG_FOREGROUND_SERVICE) != 0 + || (flags & FLAG_USER_INITIATED_JOB) != 0) { return false; } } @@ -8002,7 +8050,7 @@ public class NotificationManagerService extends SystemService { } FlagChecker childrenFlagChecker = (flags) -> { - if ((flags & FLAG_FOREGROUND_SERVICE) != 0) { + if ((flags & FLAG_FOREGROUND_SERVICE) != 0 || (flags & FLAG_USER_INITIATED_JOB) != 0) { return false; } return true; diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index 9e91875bfd86..ffe33a832ce4 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -383,6 +383,7 @@ public class NotificationUsageStats { public int numWithBigText; public int numWithBigPicture; public int numForegroundService; + public int numUserInitiatedJob; public int numOngoing; public int numAutoCancel; public int numWithLargeIcon; @@ -433,6 +434,10 @@ public class NotificationUsageStats { numForegroundService++; } + if ((n.flags & Notification.FLAG_USER_INITIATED_JOB) != 0) { + numUserInitiatedJob++; + } + if ((n.flags & Notification.FLAG_ONGOING_EVENT) != 0) { numOngoing++; } @@ -516,6 +521,7 @@ public class NotificationUsageStats { maybeCount("note_big_text", (numWithBigText - previous.numWithBigText)); maybeCount("note_big_pic", (numWithBigPicture - previous.numWithBigPicture)); maybeCount("note_fg", (numForegroundService - previous.numForegroundService)); + maybeCount("note_uij", (numUserInitiatedJob - previous.numUserInitiatedJob)); maybeCount("note_ongoing", (numOngoing - previous.numOngoing)); maybeCount("note_auto", (numAutoCancel - previous.numAutoCancel)); maybeCount("note_large_icon", (numWithLargeIcon - previous.numWithLargeIcon)); @@ -550,6 +556,7 @@ public class NotificationUsageStats { previous.numWithBigText = numWithBigText; previous.numWithBigPicture = numWithBigPicture; previous.numForegroundService = numForegroundService; + previous.numUserInitiatedJob = numUserInitiatedJob; previous.numOngoing = numOngoing; previous.numAutoCancel = numAutoCancel; previous.numWithLargeIcon = numWithLargeIcon; @@ -645,6 +652,8 @@ public class NotificationUsageStats { output.append(indentPlusTwo); output.append("numForegroundService=").append(numForegroundService).append("\n"); output.append(indentPlusTwo); + output.append("numUserInitiatedJob=").append(numUserInitiatedJob).append("\n"); + output.append(indentPlusTwo); output.append("numOngoing=").append(numOngoing).append("\n"); output.append(indentPlusTwo); output.append("numAutoCancel=").append(numAutoCancel).append("\n"); @@ -701,6 +710,7 @@ public class NotificationUsageStats { maybePut(dump, "numWithBigText", numWithBigText); maybePut(dump, "numWithBigPicture", numWithBigPicture); maybePut(dump, "numForegroundService", numForegroundService); + maybePut(dump, "numUserInitiatedJob", numUserInitiatedJob); maybePut(dump, "numOngoing", numOngoing); maybePut(dump, "numAutoCancel", numAutoCancel); maybePut(dump, "numWithLargeIcon", numWithLargeIcon); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 84bee50b77b0..402fb30437b0 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.app.ActivityOptions.KEY_SPLASH_SCREEN_THEME; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; @@ -25,6 +26,8 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; +import static android.content.PermissionChecker.PERMISSION_GRANTED; +import static android.content.PermissionChecker.checkCallingOrSelfPermissionForPreflight; import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS; import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; @@ -32,6 +35,7 @@ import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS; import android.annotation.AppIdInt; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -90,6 +94,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import android.util.Slog; +import android.window.IDumpCallback; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -104,6 +109,15 @@ import com.android.server.SystemService; import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.ActivityTaskManagerInternal; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -111,6 +125,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; /** @@ -118,6 +133,15 @@ import java.util.concurrent.ExecutionException; * managed profiles. */ public class LauncherAppsService extends SystemService { + private static final String WM_TRACE_DIR = "/data/misc/wmtrace/"; + private static final String VC_FILE_SUFFIX = ".vc"; + + private static final Set<PosixFilePermission> WM_TRACE_FILE_PERMISSIONS = Set.of( + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OWNER_READ + ); private final LauncherAppsImpl mLauncherAppsImpl; @@ -191,6 +215,8 @@ public class LauncherAppsService extends SystemService { final LauncherAppsServiceInternal mInternal; + private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>(); + public LauncherAppsImpl(Context context) { mContext = context; mIPM = AppGlobals.getPackageManager(); @@ -1431,6 +1457,66 @@ public class LauncherAppsService extends SystemService { getActivityOptionsForLauncher(opts), user.getIdentifier()); } + + /** + * Using a pipe, outputs view capture data to the wmtrace dir + */ + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dump(fd, pw, args); + + // Before the wmtrace directory is picked up by dumpstate service, some processes need + // to write their data to that location. They can do that via these dumpCallbacks. + int i = mDumpCallbacks.beginBroadcast(); + while (i > 0) { + i--; + dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i, + mDumpCallbacks.getBroadcastItem(i)); + } + mDumpCallbacks.finishBroadcast(); + } + + private void dumpDataToWmTrace(String name, IDumpCallback cb) { + ParcelFileDescriptor[] pipe; + try { + pipe = ParcelFileDescriptor.createPipe(); + cb.onDump(pipe[1]); + } catch (IOException | RemoteException e) { + Log.d(TAG, "failed to pipe view capture data", e); + return; + } + + Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName()); + try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) { + Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING); + Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS); + } catch (IOException e) { + Log.d(TAG, "failed to write data to file in wmtrace dir", e); + } + } + + @RequiresPermission(READ_FRAME_BUFFER) + @Override + public void registerDumpCallback(IDumpCallback cb) { + int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); + if (PERMISSION_GRANTED == status) { + String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid()); + mDumpCallbacks.register(cb, name); + } else { + Log.w(TAG, "caller lacks permissions to registerDumpCallback"); + } + } + + @RequiresPermission(READ_FRAME_BUFFER) + @Override + public void unRegisterDumpCallback(IDumpCallback cb) { + int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER); + if (PERMISSION_GRANTED == status) { + mDumpCallbacks.unregister(cb); + } else { + Log.w(TAG, "caller lacks permissions to unRegisterDumpCallback"); + } + } + /** Checks if user is a profile of or same as listeningUser. * and the user is enabled. */ private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user, diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 69e92e075b51..f358ce796fcf 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -677,7 +677,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements ? params.installerPackageName : installerPackageName; if (PackageManagerServiceUtils.isRootOrShell(callingUid) - || PackageInstallerSession.isSystemDataLoaderInstallation(params)) { + || PackageInstallerSession.isSystemDataLoaderInstallation(params) + || PackageManagerServiceUtils.isAdoptedShell(callingUid, mContext)) { params.installFlags |= PackageManager.INSTALL_FROM_ADB; // adb installs can override the installingPackageName, but not the // initiatingPackageName diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index ea6383e14969..006d7c82398a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -745,6 +745,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private int mValidatedTargetSdk = INVALID_TARGET_SDK_VERSION; + @GuardedBy("mLock") + private boolean mAllowsUpdateOwnership = true; + private static final FileFilter sAddedApkFilter = new FileFilter() { @Override public boolean accept(File file) { @@ -866,13 +869,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final int USER_ACTION_NOT_NEEDED = 0; private static final int USER_ACTION_REQUIRED = 1; - private static final int USER_ACTION_PENDING_APK_PARSING = 2; private static final int USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER = 3; @IntDef({ USER_ACTION_NOT_NEEDED, USER_ACTION_REQUIRED, - USER_ACTION_PENDING_APK_PARSING, USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER, }) @interface UserActionRequirement {} @@ -963,11 +964,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { && !isApexSession() && !isUpdateOwner && !isInstallerShell + && mAllowsUpdateOwnership // We don't enforce the update ownership for the managed user and profile. && !isFromManagedUserOrProfile) { return USER_ACTION_REQUIRED_UPDATE_OWNER_REMINDER; } - if (isPermissionGranted) { return USER_ACTION_NOT_NEEDED; } @@ -982,7 +983,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { && isUpdateWithoutUserActionPermissionGranted && ((isUpdateOwnershipEnforcementEnabled ? isUpdateOwner : isInstallerOfRecord) || isSelfUpdate)) { - return USER_ACTION_PENDING_APK_PARSING; + if (!isApexSession()) { + if (!isTargetSdkConditionSatisfied(this)) { + return USER_ACTION_REQUIRED; + } + + if (!mSilentUpdatePolicy.isSilentUpdateAllowed( + getInstallerPackageName(), getPackageName())) { + // Fall back to the non-silent update if a repeated installation is invoked + // within the throttle time. + return USER_ACTION_REQUIRED; + } + mSilentUpdatePolicy.track(getInstallerPackageName(), getPackageName()); + return USER_ACTION_NOT_NEEDED; + } } return USER_ACTION_REQUIRED; @@ -1442,7 +1456,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) { assertCallerIsOwnerRootOrVerifier(); final File file = new File(stageDir, name); - final String installerPackageName = getInstallSource().mInitiatingPackageName; + final String installerPackageName = PackageManagerServiceUtils.isInstalledByAdb( + getInstallSource().mInitiatingPackageName) + ? getInstallSource().mInstallerPackageName + : getInstallSource().mInitiatingPackageName; try { mPm.requestFileChecksums(file, installerPackageName, optional, required, trustedInstallers, onChecksumsReadyListener); @@ -2363,26 +2380,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { session.sendPendingUserActionIntent(target); return true; } - - if (!session.isApexSession() && userActionRequirement == USER_ACTION_PENDING_APK_PARSING) { - if (!isTargetSdkConditionSatisfied(session)) { - session.sendPendingUserActionIntent(target); - return true; - } - - if (session.params.requireUserAction == SessionParams.USER_ACTION_NOT_REQUIRED) { - if (!session.mSilentUpdatePolicy.isSilentUpdateAllowed( - session.getInstallerPackageName(), session.getPackageName())) { - // Fall back to the non-silent update if a repeated installation is invoked - // within the throttle time. - session.sendPendingUserActionIntent(target); - return true; - } - session.mSilentUpdatePolicy.track(session.getInstallerPackageName(), - session.getPackageName()); - } - } - return false; } @@ -3393,6 +3390,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // {@link PackageLite#getTargetSdk()} mValidatedTargetSdk = packageLite.getTargetSdk(); + mAllowsUpdateOwnership = packageLite.isAllowUpdateOwnership(); + return packageLite; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6b213b78f11c..e4e3a9d0b7d3 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7273,7 +7273,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService final long token = Binder.clearCallingIdentity(); try { return DeviceConfig.getBoolean(NAMESPACE_PACKAGE_MANAGER_SERVICE, - PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, false /* defaultValue */); + PROPERTY_IS_UPDATE_OWNERSHIP_ENFORCEMENT_AVAILABLE, true /* defaultValue */); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 42538f33c5f8..db997d8d1d79 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -36,6 +36,7 @@ import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.STUB_SUFFIX; import static com.android.server.pm.PackageManagerService.TAG; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1389,6 +1390,14 @@ public class PackageManagerServiceUtils { } /** + * Check if a UID is non-system UID adopted shell permission. + */ + public static boolean isAdoptedShell(int uid, Context context) { + return uid != Process.SYSTEM_UID && context.checkCallingOrSelfPermission( + Manifest.permission.USE_SYSTEM_DATA_LOADERS) == PackageManager.PERMISSION_GRANTED; + } + + /** * Check if a UID is system UID or shell's UID. */ public static boolean isRootOrShell(int uid) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index cc60802967b0..89f46fed4862 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -692,7 +692,7 @@ class PackageManagerShellCommand extends ShellCommand { null /* usesSplitNames */, null /* configForSplit */, null /* splitApkPaths */, null /* splitRevisionCodes */, apkLite.getTargetSdkVersion(), null /* requiredSplitTypes */, - null /* splitTypes */); + null /* splitTypes */, apkLite.isAllowUpdateOwnership()); sessionSize += InstallLocationUtils.calculateInstalledSize(pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()); } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index b7a2b86b1bcd..a814ca46fa5e 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -136,6 +136,7 @@ public final class UserTypeFactory { com.android.internal.R.color.system_neutral2_900) .setDefaultRestrictions(null) .setDefaultCrossProfileIntentFilters(getDefaultCloneCrossProfileIntentFilter()) + .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) .setDefaultUserProperties(new UserProperties.Builder() .setStartWithParent(true) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT) @@ -216,7 +217,8 @@ public final class UserTypeFactory { com.android.internal.R.color.profile_badge_1_dark, com.android.internal.R.color.profile_badge_2_dark, com.android.internal.R.color.profile_badge_3_dark) - .setDefaultRestrictions(restrictions); + .setDefaultRestrictions(restrictions) + .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()); } /** @@ -337,6 +339,15 @@ public final class UserTypeFactory { return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters(); } + /** Gets a default bundle, keyed by Settings.Secure String names, for non-managed profiles. */ + private static Bundle getDefaultNonManagedProfileSecureSettings() { + final Bundle settings = new Bundle(); + // Non-managed profiles go through neither SetupWizard nor DPC flows, so we automatically + // mark them as setup. + settings.putString(android.provider.Settings.Secure.USER_SETUP_COMPLETE, "1"); + return settings; + } + /** * Reads the given xml parser to obtain device user-type customization, and updates the given * map of {@link UserTypeDetails.Builder}s accordingly. diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5f56923e2f53..8346e7c83190 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4068,7 +4068,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void finishRelaunching() { - mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(false); + mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(false); mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this); if (mPendingRelaunchCount > 0) { @@ -9500,7 +9500,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mRelaunchReason = RELAUNCH_REASON_NONE; } if (isRequestedOrientationChanged) { - mLetterboxUiController.setRelauchingAfterRequestedOrientationChanged(true); + mLetterboxUiController.setRelaunchingAfterRequestedOrientationChanged(true); } if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 6773bcd6fac8..e447049a7362 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -487,7 +487,7 @@ public class BackgroundActivityStartController { // The verdict changed from allow (resultIfPiSenderAllowsBal) to block, PI sender // default change is on (otherwise we would have fallen into if above) and we'd // allow if it were off - Slog.wtf(TAG, "Without BAL hardening this activity start would NOT be allowed!" + Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed!" + stateDumpLog); } } diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 13a1cb6daf38..c6db8a7acb6f 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -126,6 +126,9 @@ class Dimmer { boolean isVisible; SurfaceAnimator mSurfaceAnimator; + // TODO(b/64816140): Remove after confirming dimmer layer always matches its container. + final Rect mDimBounds = new Rect(); + /** * Determines whether the dim layer should animate before destroying. */ @@ -260,11 +263,16 @@ class Dimmer { * {@link WindowContainer#prepareSurfaces}. After calling this, the container should * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them * a chance to request dims to continue. + * @return Non-null dim bounds if the dimmer is showing. */ - void resetDimStates() { - if (mDimState != null && !mDimState.mDontReset) { + Rect resetDimStates() { + if (mDimState == null) { + return null; + } + if (!mDimState.mDontReset) { mDimState.mDimming = false; } + return mDimState.mDimBounds; } void dontAnimateExit() { @@ -275,13 +283,13 @@ class Dimmer { /** * Call after invoking {@link WindowContainer#prepareSurfaces} on children as - * described in {@link #resetDimStates}. + * described in {@link #resetDimStates}. The dim bounds returned by {@link #resetDimStates} + * should be set before calling this method. * * @param t A transaction in which to update the dims. - * @param bounds The bounds at which to dim. * @return true if any Dims were updated. */ - boolean updateDims(SurfaceControl.Transaction t, Rect bounds) { + boolean updateDims(SurfaceControl.Transaction t) { if (mDimState == null) { return false; } @@ -297,6 +305,7 @@ class Dimmer { mDimState = null; return false; } else { + final Rect bounds = mDimState.mDimBounds; // TODO: Once we use geometry from hierarchy this falls away. t.setPosition(mDimState.mDimLayer, bounds.left, bounds.top); t.setWindowCrop(mDimState.mDimLayer, bounds.width(), bounds.height()); diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index c2ddb4158846..26f56a2e5c0b 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -148,7 +148,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @ScreenOrientation int getOrientation(int candidate) { final int orientation = super.getOrientation(candidate); - if (getIgnoreOrientationRequest(orientation)) { + if (shouldIgnoreOrientationRequest(orientation)) { // In all the other case, mLastOrientationSource will be reassigned to a new value mLastOrientationSource = null; return SCREEN_ORIENTATION_UNSET; @@ -158,7 +158,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) { - return !getIgnoreOrientationRequest(orientation) + return !shouldIgnoreOrientationRequest(orientation) && super.handlesOrientationChangeFromDescendant(orientation); } @@ -169,7 +169,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { final int orientation = requestingContainer != null ? requestingContainer.getOverrideOrientation() : SCREEN_ORIENTATION_UNSET; - return !getIgnoreOrientationRequest(orientation) + return !shouldIgnoreOrientationRequest(orientation) && super.onDescendantOrientationChanged(requestingContainer); } @@ -236,8 +236,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { /** * @return {@value true} if we need to ignore the orientation in input. */ - // TODO(b/262366204): Rename getIgnoreOrientationRequest to shouldIgnoreOrientationRequest - boolean getIgnoreOrientationRequest(@ScreenOrientation int orientation) { + boolean shouldIgnoreOrientationRequest(@ScreenOrientation int orientation) { // We always respect orientation request for ActivityInfo.SCREEN_ORIENTATION_LOCKED // ActivityInfo.SCREEN_ORIENTATION_NOSENSOR. // Main use case why this is important is Camera apps that rely on those @@ -768,7 +767,6 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { */ static class Dimmable extends DisplayArea<DisplayArea> { private final Dimmer mDimmer = new Dimmer(this); - private final Rect mTmpDimBoundsRect = new Rect(); Dimmable(WindowManagerService wms, Type type, String name, int featureId) { super(wms, type, name, featureId); @@ -781,11 +779,13 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override void prepareSurfaces() { - mDimmer.resetDimStates(); + final Rect dimBounds = mDimmer.resetDimStates(); super.prepareSurfaces(); - // Bounds need to be relative, as the dim layer is a child. - getBounds(mTmpDimBoundsRect); - mTmpDimBoundsRect.offsetTo(0 /* newLeft */, 0 /* newTop */); + if (dimBounds != null) { + // Bounds need to be relative, as the dim layer is a child. + getBounds(dimBounds); + dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); + } // If SystemUI is dragging for recents, we want to reset the dim state so any dim layer // on the display level fades out. @@ -793,8 +793,10 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { mDimmer.resetDimStates(); } - if (mDimmer.updateDims(getSyncTransaction(), mTmpDimBoundsRect)) { - scheduleAnimation(); + if (dimBounds != null) { + if (mDimmer.updateDims(getSyncTransaction())) { + scheduleAnimation(); + } } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ab109fcaa673..bec58b848478 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1648,7 +1648,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Override boolean handlesOrientationChangeFromDescendant(@ScreenOrientation int orientation) { - return !getIgnoreOrientationRequest(orientation) + return !shouldIgnoreOrientationRequest(orientation) && !getDisplayRotation().isFixedToUserRotation(); } @@ -1752,7 +1752,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } final int activityOrientation = r.getOverrideOrientation(); if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM - || getIgnoreOrientationRequest(activityOrientation)) { + || shouldIgnoreOrientationRequest(activityOrientation)) { return ROTATION_UNDEFINED; } if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { @@ -5149,7 +5149,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @ScreenOrientation int getOrientation(@ScreenOrientation int candidate) { // IME does not participate in orientation. - return getIgnoreOrientationRequest(candidate) ? SCREEN_ORIENTATION_UNSET : candidate; + return shouldIgnoreOrientationRequest(candidate) ? SCREEN_ORIENTATION_UNSET : candidate; } @Override diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index d65f464590c1..44d67687e260 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -292,7 +292,9 @@ class EmbeddedWindowController { private void handleTap(boolean grantFocus) { if (mInputChannel != null) { if (mHostWindowState != null) { - mWmService.grantEmbeddedWindowFocus(mSession, mHostWindowState.mClient, + // Use null session since this is being granted by system server and doesn't + // require the host session to be passed in + mWmService.grantEmbeddedWindowFocus(null, mHostWindowState.mClient, mFocusGrantToken, grantFocus); if (grantFocus) { // If granting focus to the embedded when tapped, we need to ensure the host diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index bb50372ba019..bf511adf0bf9 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -263,8 +263,8 @@ class LaunchParamsPersister { boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId); params.mDisplayUniqueId = info.uniqueId; - changed |= params.mWindowingMode != task.getTaskDisplayArea().getWindowingMode(); - params.mWindowingMode = task.getTaskDisplayArea().getWindowingMode(); + changed |= params.mWindowingMode != task.getWindowingMode(); + params.mWindowingMode = task.getWindowingMode(); if (task.mLastNonFullscreenBounds != null) { changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index b0c384dcd7ac..93233dd4bda8 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -50,6 +50,7 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_V import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -235,9 +236,14 @@ final class LetterboxUiController { private final Boolean mBooleanPropertyIgnoreRequestedOrientation; @Nullable + private final Boolean mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected; + + @Nullable private final Boolean mBooleanPropertyFakeFocus; - private boolean mIsRelauchingAfterRequestedOrientationChanged; + private boolean mIsRelaunchingAfterRequestedOrientationChanged; + + private boolean mLastShouldShowLetterboxUi; private boolean mDoubleTapEvent; @@ -253,6 +259,10 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); + mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected = + readComponentProperty(packageManager, mActivityRecord.packageName, + mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, + PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); mBooleanPropertyFakeFocus = readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isCompatFakeFocusEnabled, @@ -387,7 +397,7 @@ final class LetterboxUiController { ::isPolicyForIgnoringRequestedOrientationEnabled, mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, mBooleanPropertyIgnoreRequestedOrientation)) { - if (mIsRelauchingAfterRequestedOrientationChanged) { + if (mIsRelaunchingAfterRequestedOrientationChanged) { Slog.w(TAG, "Ignoring orientation update to " + screenOrientationToString(requestedOrientation) + " due to relaunching after setRequestedOrientation for " @@ -422,6 +432,8 @@ final class LetterboxUiController { * * <p>This treatment is enabled when the following conditions are met: * <ul> + * <li>Flag gating the treatment is enabled + * <li>Opt-out component property isn't enabled * <li>Per-app override is enabled * <li>App has requested orientation more than 2 times within 1-second * timer and activity is not letterboxed for fixed orientation @@ -429,7 +441,11 @@ final class LetterboxUiController { */ @VisibleForTesting boolean shouldIgnoreOrientationRequestLoop() { - if (!mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled) { + if (!shouldEnableWithOptInOverrideAndOptOutProperty( + /* gatingCondition */ mLetterboxConfiguration + ::isPolicyForIgnoringRequestedOrientationEnabled, + mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled, + mBooleanPropertyIgnoreOrientationRequestWhenLoopDetected)) { return false; } @@ -476,8 +492,8 @@ final class LetterboxUiController { * Sets whether an activity is relaunching after the app has called {@link * android.app.Activity#setRequestedOrientation}. */ - void setRelauchingAfterRequestedOrientationChanged(boolean isRelaunching) { - mIsRelauchingAfterRequestedOrientationChanged = isRelaunching; + void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) { + mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching; } /** @@ -1154,12 +1170,28 @@ final class LetterboxUiController { @VisibleForTesting boolean shouldShowLetterboxUi(WindowState mainWindow) { - return (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow)) + if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) { + return mLastShouldShowLetterboxUi; + } + + final boolean shouldShowLetterboxUi = + (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow)) && mainWindow.areAppWindowBoundsLetterboxed() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this // activity is using blurred wallpaper for letterbox background. && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0; + + mLastShouldShowLetterboxUi = shouldShowLetterboxUi; + + return shouldShowLetterboxUi; + } + + @VisibleForTesting + boolean isSurfaceReadyToShow(WindowState mainWindow) { + return mainWindow.isDrawn() // Regular case + // Waiting for relayoutWindow to call preserveSurface + || mainWindow.isDragResizeChanged(); } @VisibleForTesting @@ -1297,6 +1329,10 @@ final class LetterboxUiController { return null; } + boolean getIsRelaunchingAfterRequestedOrientationChanged() { + return mIsRelaunchingAfterRequestedOrientationChanged; + } + private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) { // Rounded corners should be displayed above the taskbar. When taskbar is hidden, // an insets frame is equal to a navigation bar which shouldn't affect position of diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index b38666522754..f8f0211e108f 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -338,7 +338,8 @@ class RecentTasks { synchronized (mService.mGlobalLock) { final Task focusedStack = mService.getTopDisplayFocusedRootTask(); final Task topTask = focusedStack != null ? focusedStack.getTopMostTask() : null; - resetFreezeTaskListReordering(topTask); + final Task reorderToEndTask = topTask != null && topTask.hasChild() ? topTask : null; + resetFreezeTaskListReordering(reorderToEndTask); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index db4453297001..0857898ca1d2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -481,8 +481,6 @@ class Task extends TaskFragment { // to layout without loading all the task snapshots final PersistedTaskSnapshotData mLastTaskSnapshotData; - private final Rect mTmpDimBoundsRect = new Rect(); - /** @see #setCanAffectSystemUiFlags */ private boolean mCanAffectSystemUiFlags = true; @@ -3254,22 +3252,24 @@ class Task extends TaskFragment { @Override void prepareSurfaces() { - mDimmer.resetDimStates(); + final Rect dimBounds = mDimmer.resetDimStates(); super.prepareSurfaces(); - getDimBounds(mTmpDimBoundsRect); - // Bounds need to be relative, as the dim layer is a child. - if (inFreeformWindowingMode()) { - getBounds(mTmpRect); - mTmpDimBoundsRect.offsetTo(mTmpDimBoundsRect.left - mTmpRect.left, - mTmpDimBoundsRect.top - mTmpRect.top); - } else { - mTmpDimBoundsRect.offsetTo(0, 0); + if (dimBounds != null) { + getDimBounds(dimBounds); + + // Bounds need to be relative, as the dim layer is a child. + if (inFreeformWindowingMode()) { + getBounds(mTmpRect); + dimBounds.offsetTo(dimBounds.left - mTmpRect.left, dimBounds.top - mTmpRect.top); + } else { + dimBounds.offsetTo(0, 0); + } } final SurfaceControl.Transaction t = getSyncTransaction(); - if (mDimmer.updateDims(t, mTmpDimBoundsRect)) { + if (dimBounds != null && mDimmer.updateDims(t)) { scheduleAnimation(); } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 76759ba53f5a..b0a879e96dcf 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1883,7 +1883,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Only allow to specify orientation if this TDA is the last focused one on this logical // display that can request orientation request. return mDisplayContent.getOrientationRequestingTaskDisplayArea() == this - && !getIgnoreOrientationRequest(orientation); + && !shouldIgnoreOrientationRequest(orientation); } void clearPreferredTopFocusableRootTask() { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 612fc4be70db..1d232fe99e3c 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2923,14 +2923,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { return; } - mDimmer.resetDimStates(); + final Rect dimBounds = mDimmer.resetDimStates(); super.prepareSurfaces(); - // Bounds need to be relative, as the dim layer is a child. - final Rect dimBounds = getBounds(); - dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); - if (mDimmer.updateDims(getSyncTransaction(), dimBounds)) { - scheduleAnimation(); + if (dimBounds != null) { + // Bounds need to be relative, as the dim layer is a child. + dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); + if (mDimmer.updateDims(getSyncTransaction())) { + scheduleAnimation(); + } } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 0fe1f923e4e5..1fb353476592 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -33,8 +33,6 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; -import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE; -import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -167,6 +165,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private SurfaceControl.Transaction mStartTransaction = null; private SurfaceControl.Transaction mFinishTransaction = null; + /** Used for failsafe clean-up to prevent leaks due to misbehaving player impls. */ + private SurfaceControl.Transaction mCleanupTransaction = null; + /** * Contains change infos for both participants and all remote-animatable ancestors. The * ancestors can be the promotion candidates so their start-states need to be captured. @@ -787,6 +788,24 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** + * Build a transaction that cleans-up transition-only surfaces (transition root and snapshots). + * This will ALWAYS be applied on transition finish just in-case + */ + private static void buildCleanupTransaction(SurfaceControl.Transaction t, TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change c = info.getChanges().get(i); + if (c.getSnapshot() != null) { + t.reparent(c.getSnapshot(), null); + } + } + for (int i = info.getRootCount() - 1; i >= 0; --i) { + final SurfaceControl leash = info.getRoot(i).getLeash(); + if (leash == null) continue; + t.reparent(leash, null); + } + } + + /** * Set whether this transition can start a pip-enter transition when finished. This is usually * true, but gets set to false when recents decides that it wants to finish its animation but * not actually finish its animation (yeah...). @@ -863,6 +882,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mStartTransaction != null) mStartTransaction.close(); if (mFinishTransaction != null) mFinishTransaction.close(); mStartTransaction = mFinishTransaction = null; + if (mCleanupTransaction != null) { + mCleanupTransaction.apply(); + mCleanupTransaction = null; + } if (mState < STATE_PLAYING) { throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); } @@ -1286,6 +1309,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } buildFinishTransaction(mFinishTransaction, info); + mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); + buildCleanupTransaction(mCleanupTransaction, info); if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) { mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay); try { @@ -1385,6 +1410,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { ci.mSnapshot.release(); } } + if (mCleanupTransaction != null) { + mCleanupTransaction.apply(); + mCleanupTransaction = null; + } } /** The transition is ready to play. Make the start transaction show the surfaces. */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 23934e00408a..6e3924baadf3 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5177,13 +5177,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override void prepareSurfaces() { mIsDimming = false; - applyDims(); - updateSurfacePositionNonOrganized(); - // Send information to SurfaceFlinger about the priority of the current window. - updateFrameRateSelectionPriorityIfNeeded(); - updateScaleIfNeeded(); - - mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); + if (mHasSurface) { + applyDims(); + updateSurfacePositionNonOrganized(); + // Send information to SurfaceFlinger about the priority of the current window. + updateFrameRateSelectionPriorityIfNeeded(); + updateScaleIfNeeded(); + mWinAnimator.prepareSurfaceLocked(getSyncTransaction()); + } super.prepareSurfaces(); } @@ -5230,8 +5231,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (surfaceInsetsChanged) { mLastSurfaceInsets.set(mAttrs.surfaceInsets); } - if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation() - && okToDisplay() && mSyncState == SYNC_STATE_NONE) { + final boolean surfaceResizedWithoutMoveAnimation = surfaceSizeChanged + && mWinAnimator.getShown() && !canPlayMoveAnimation() && okToDisplay() + && mSyncState == SYNC_STATE_NONE; + final ActivityRecord activityRecord = getActivityRecord(); + // If this window belongs to an activity that is relaunching due to an orientation + // change then delay the position update until it has redrawn to avoid any flickers. + final boolean isLetterboxedAndRelaunching = activityRecord != null + && activityRecord.areBoundsLetterboxed() + && activityRecord.mLetterboxUiController + .getIsRelaunchingAfterRequestedOrientationChanged(); + if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) { applyWithNextDraw(mSetSurfacePositionConsumer); } else { mSetSurfacePositionConsumer.accept(t); diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index dce7b87c0328..5c77aa22ece8 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -30,6 +30,7 @@ import android.service.credentials.CallingAppInfo; import android.util.Log; import java.util.ArrayList; +import java.util.Set; /** * Central session for a single clearCredentialState request. This class listens to the @@ -40,12 +41,15 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta implements ProviderSession.ProviderInternalCallback<Void> { private static final String TAG = "GetRequestSession"; - public ClearRequestSession(Context context, int userId, int callingUid, + public ClearRequestSession(Context context, RequestSession.SessionLifetime sessionCallback, + Object lock, int userId, int callingUid, IClearCredentialStateCallback callback, ClearCredentialStateRequest request, - CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, + CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, + CancellationSignal cancellationSignal, long startedTimestamp) { - super(context, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED, - callingAppInfo, cancellationSignal, startedTimestamp); + super(context, sessionCallback, lock, userId, callingUid, request, callback, + RequestInfo.TYPE_UNDEFINED, + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); } /** diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 98dc8ab8aa9c..02aaf867fa7b 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -38,6 +38,7 @@ import android.util.Log; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; +import java.util.Set; /** * Central session for a single {@link CredentialManager#createCredential} request. @@ -49,14 +50,17 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> { private static final String TAG = "CreateRequestSession"; - CreateRequestSession(@NonNull Context context, int userId, int callingUid, + CreateRequestSession(@NonNull Context context, RequestSession.SessionLifetime sessionCallback, + Object lock, int userId, int callingUid, CreateCredentialRequest request, ICreateCredentialCallback callback, CallingAppInfo callingAppInfo, + Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long startedTimestamp) { - super(context, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE, - callingAppInfo, cancellationSignal, startedTimestamp); + super(context, sessionCallback, lock, userId, callingUid, request, callback, + RequestInfo.TYPE_CREATE, + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); } /** @@ -83,6 +87,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); + mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION); try { mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( RequestInfo.newCreateRequestInfo( @@ -93,6 +98,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR providerDataList)); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); + mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); respondToClientWithErrorAndFinish( CreateCredentialException.TYPE_UNKNOWN, "Unable to invoke selector"); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index de06d440fa9d..9320dd247380 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -33,7 +33,6 @@ import android.content.pm.PackageManager; import android.credentials.ClearCredentialStateRequest; import android.credentials.CreateCredentialException; import android.credentials.CreateCredentialRequest; -import android.credentials.CredentialManager; import android.credentials.CredentialOption; import android.credentials.CredentialProviderInfo; import android.credentials.GetCredentialException; @@ -50,6 +49,7 @@ import android.credentials.UnregisterCredentialDescriptionRequest; import android.credentials.ui.IntentFactory; import android.os.Binder; import android.os.CancellationSignal; +import android.os.IBinder; import android.os.ICancellationSignal; import android.os.RemoteException; import android.os.UserHandle; @@ -70,9 +70,11 @@ import com.android.server.infra.AbstractMasterSystemService; import com.android.server.infra.SecureSettingsServiceNameResolver; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -94,6 +96,8 @@ public final class CredentialManagerService private static final String PERMISSION_DENIED_ERROR = "permission_denied"; private static final String PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR = "Caller is missing WRITE_SECURE_SETTINGS permission"; + private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER = + "enable_credential_manager"; private final Context mContext; @@ -102,6 +106,13 @@ public final class CredentialManagerService private final SparseArray<List<CredentialManagerServiceImpl>> mSystemServicesCacheList = new SparseArray<>(); + /** Cache of all ongoing request sessions per user id. */ + @GuardedBy("mLock") + private final SparseArray<Map<IBinder, RequestSession>> mRequestSessions = + new SparseArray<>(); + + private final SessionManager mSessionManager = new SessionManager(); + public CredentialManagerService(@NonNull Context context) { super( context, @@ -331,7 +342,7 @@ public final class CredentialManagerService @NonNull private Set<Pair<CredentialOption, CredentialDescriptionRegistry.FilterResult>> - getFilteredResultFromRegistry(List<CredentialOption> options) { + getFilteredResultFromRegistry(List<CredentialOption> options) { // Session for active/provisioned credential descriptions; CredentialDescriptionRegistry registry = CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId()); @@ -389,14 +400,6 @@ public final class CredentialManagerService return providerSessions; } - private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) { - return CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, - userId, - CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS, - new HashSet<>()); - } - @Override @GuardedBy("CredentialDescriptionRegistry.sLock") public void onUserStopped(@NonNull TargetUser user) { @@ -448,13 +451,17 @@ public final class CredentialManagerService final GetRequestSession session = new GetRequestSession( getContext(), + mSessionManager, + mLock, userId, callingUid, callback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); + addSessionLocked(userId, session); List<ProviderSession> providerSessions = prepareProviderSessions(request, session); @@ -499,11 +506,14 @@ public final class CredentialManagerService final PrepareGetRequestSession session = new PrepareGetRequestSession( getContext(), + mSessionManager, + mLock, userId, callingUid, getCredentialCallback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan, prepareGetCredentialCallback); @@ -515,8 +525,8 @@ public final class CredentialManagerService // TODO: fix prepareGetCredentialCallback.onResponse( new PrepareGetCredentialResponseInternal( - false, null, - false, false, null)); + false, null, + false, false, null)); } catch (RemoteException e) { Log.i( TAG, @@ -540,10 +550,10 @@ public final class CredentialManagerService List<CredentialOption> optionsThatRequireActiveCredentials = request.getCredentialOptions().stream() .filter(credentialOption -> credentialOption - .getCredentialRetrievalData() - .getStringArrayList( - CredentialOption - .SUPPORTED_ELEMENT_KEYS) != null) + .getCredentialRetrievalData() + .getStringArrayList( + CredentialOption + .SUPPORTED_ELEMENT_KEYS) != null) .toList(); List<CredentialOption> optionsThatDoNotRequireActiveCredentials = @@ -614,13 +624,17 @@ public final class CredentialManagerService final CreateRequestSession session = new CreateRequestSession( getContext(), + mSessionManager, + mLock, userId, callingUid, request, callback, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); + addSessionLocked(userId, session); processCreateCredential(request, callback, session); return cancelTransport; @@ -775,6 +789,19 @@ public final class CredentialManagerService mContext, userId, providerFilter, getEnabledProviders()); } + @Override + public boolean isServiceEnabled() { + final long origId = Binder.clearCallingIdentity(); + try { + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_CREDENTIAL, + DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, + false); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + @SuppressWarnings("GuardedBy") // ErrorProne requires service.mLock which is the same // this.mLock private Set<ComponentName> getEnabledProviders() { @@ -815,13 +842,17 @@ public final class CredentialManagerService final ClearRequestSession session = new ClearRequestSession( getContext(), + mSessionManager, + mLock, userId, callingUid, callback, request, constructCallingAppInfo(callingPackage, userId, null), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); + addSessionLocked(userId, session); // Initiate all provider sessions // TODO: Determine if provider needs to have clear capability in their manifest @@ -905,6 +936,13 @@ public final class CredentialManagerService } } + private void addSessionLocked(@UserIdInt int userId, + RequestSession requestSession) { + synchronized (mLock) { + mSessionManager.addSession(userId, requestSession.mRequestId, requestSession); + } + } + private void enforceCallingPackage(String callingPackage, int callingUid) { int packageUid; PackageManager pm = mContext.createContextAsUser( @@ -919,4 +957,23 @@ public final class CredentialManagerService throw new SecurityException(callingPackage + " does not belong to uid " + callingUid); } } + + private class SessionManager implements RequestSession.SessionLifetime { + @Override + @GuardedBy("mLock") + public void onFinishRequestSession(@UserIdInt int userId, IBinder token) { + Log.i(TAG, "In onFinishRequestSession"); + if (mRequestSessions.get(userId) != null) { + mRequestSessions.get(userId).remove(token); + } + } + + @GuardedBy("mLock") + public void addSession(int userId, IBinder token, RequestSession requestSession) { + if (mRequestSessions.get(userId) == null) { + mRequestSessions.put(userId, new HashMap<>()); + } + mRequestSessions.get(userId).put(token, requestSession); + } + } } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 546c37ff95af..87509067f993 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -20,7 +20,6 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ServiceInfo; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.ui.DisabledProviderData; @@ -30,6 +29,7 @@ import android.credentials.ui.RequestInfo; import android.credentials.ui.UserSelectionDialogResult; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.ResultReceiver; import android.service.credentials.CredentialProviderInfoFactory; @@ -37,20 +37,32 @@ import android.util.Log; import android.util.Slog; import java.util.ArrayList; -import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** Initiates the Credential Manager UI and receives results. */ public class CredentialManagerUi { private static final String TAG = "CredentialManagerUi"; @NonNull private final CredentialManagerUiCallback mCallbacks; - @NonNull private final Context mContext; + @NonNull + private final Context mContext; // TODO : Use for starting the activity for this user private final int mUserId; - @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver( + + private UiStatus mStatus; + + private final Set<ComponentName> mEnabledProviders; + + enum UiStatus { + IN_PROGRESS, + USER_INTERACTION, + NOT_STARTED, TERMINATED + } + + @NonNull + private final ResultReceiver mResultReceiver = new ResultReceiver( new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -61,6 +73,7 @@ public class CredentialManagerUi { private void handleUiResult(int resultCode, Bundle resultData) { switch (resultCode) { case UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION: + mStatus = UiStatus.IN_PROGRESS; UserSelectionDialogResult selection = UserSelectionDialogResult .fromResultData(resultData); if (selection != null) { @@ -70,75 +83,90 @@ public class CredentialManagerUi { } break; case UserSelectionDialogResult.RESULT_CODE_DIALOG_USER_CANCELED: + mStatus = UiStatus.TERMINATED; mCallbacks.onUiCancellation(/* isUserCancellation= */ true); break; case UserSelectionDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS: + mStatus = UiStatus.TERMINATED; mCallbacks.onUiCancellation(/* isUserCancellation= */ false); break; case UserSelectionDialogResult.RESULT_CODE_DATA_PARSING_FAILURE: + mStatus = UiStatus.TERMINATED; mCallbacks.onUiSelectorInvocationFailure(); break; default: Slog.i(TAG, "Unknown error code returned from the UI"); + mStatus = UiStatus.IN_PROGRESS; mCallbacks.onUiSelectorInvocationFailure(); break; } } + /** Creates intent that is ot be invoked to cancel an in-progress UI session. */ + public Intent createCancelIntent(IBinder requestId, String packageName) { + return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true, + packageName); + } + /** * Interface to be implemented by any class that wishes to get callbacks from the UI. */ public interface CredentialManagerUiCallback { /** Called when the user makes a selection. */ void onUiSelection(UserSelectionDialogResult selection); + /** Called when the UI is canceled without a successful provider result. */ void onUiCancellation(boolean isUserCancellation); /** Called when the selector UI fails to come up (mostly due to parsing issue today). */ void onUiSelectorInvocationFailure(); } + public CredentialManagerUi(Context context, int userId, - CredentialManagerUiCallback callbacks) { + CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) { Log.i(TAG, "In CredentialManagerUi constructor"); mContext = context; mUserId = userId; mCallbacks = callbacks; + mEnabledProviders = enabledProviders; + mStatus = UiStatus.IN_PROGRESS; + } + + /** Set status for credential manager UI */ + public void setStatus(UiStatus status) { + mStatus = status; + } + + /** Returns status for credential manager UI */ + public UiStatus getStatus() { + return mStatus; } /** * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI, * by the calling app process. - * @param requestInfo the information about the request + * + * @param requestInfo the information about the request * @param providerDataList the list of provider data from remote providers */ public PendingIntent createPendingIntent( RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) { Log.i(TAG, "In createPendingIntent"); - ArrayList<DisabledProviderData> disabledProviderDataList = new ArrayList<>(); - Set<String> enabledProviders = providerDataList.stream() - .map(ProviderData::getProviderFlattenedComponentName) - .collect(Collectors.toUnmodifiableSet()); - Set<String> allProviders = + List<CredentialProviderInfo> allProviders = CredentialProviderInfoFactory.getCredentialProviderServices( - mContext, - mUserId, - CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, - new HashSet<>()) - .stream() - .map(CredentialProviderInfo::getServiceInfo) - .map(ServiceInfo::getComponentName) - .map(ComponentName::flattenToString) - .collect(Collectors.toUnmodifiableSet()); - - for (String provider: allProviders) { - if (!enabledProviders.contains(provider)) { - disabledProviderDataList.add(new DisabledProviderData(provider)); - } - } + mContext, + mUserId, + CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY, + mEnabledProviders); + + List<DisabledProviderData> disabledProviderDataList = allProviders.stream() + .filter(provider -> !provider.isEnabled()) + .map(disabledProvider -> new DisabledProviderData( + disabledProvider.getComponentName().flattenToString())).toList(); Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList, - disabledProviderDataList, mResultReceiver) + new ArrayList<>(disabledProviderDataList), mResultReceiver) .setAction(UUID.randomUUID().toString()); //TODO: Create unique pending intent using request code and cancel any pre-existing pending // intents diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index c0c7be9d80e2..c44e665ba699 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -35,6 +35,7 @@ import android.util.Log; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; +import java.util.Set; import java.util.stream.Collectors; /** @@ -45,21 +46,26 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, IGetCredentialCallback, GetCredentialResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetRequestSession"; - public GetRequestSession(Context context, int userId, int callingUid, + + public GetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback, + Object lock, int userId, int callingUid, IGetCredentialCallback callback, GetCredentialRequest request, - CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, + CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, + CancellationSignal cancellationSignal, long startedTimestamp) { - super(context, userId, callingUid, request, callback, RequestInfo.TYPE_GET, - callingAppInfo, cancellationSignal, startedTimestamp); + super(context, sessionCallback, lock, userId, callingUid, request, callback, + RequestInfo.TYPE_GET, callingAppInfo, enabledProviders, cancellationSignal, + startedTimestamp); int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( - Collectors.toSet())).size(); // Dedupe type strings + Collectors.toSet())).size(); // Dedupe type strings mRequestSessionMetric.collectGetFlowInitialMetricInfo(numTypes); } /** * Creates a new provider session, and adds it list of providers that are contributing to * this session. + * * @return the provider session created within this request session, for the given provider * info. */ @@ -81,6 +87,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); + mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION); try { mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( RequestInfo.newGetRequestInfo( @@ -88,6 +95,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, providerDataList)); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); + mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); respondToClientWithErrorAndFinish( GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector"); } @@ -146,7 +154,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onUiSelectorInvocationFailure() { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, - "No credentials available."); + "No credentials available."); } @Override diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index c4e480a8e609..f274e65a20c3 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -49,14 +49,14 @@ public class PrepareGetRequestSession extends GetRequestSession { private final IPrepareGetCredentialCallback mPrepareGetCredentialCallback; - public PrepareGetRequestSession(Context context, int userId, int callingUid, - IGetCredentialCallback callback, - GetCredentialRequest request, - CallingAppInfo callingAppInfo, + public PrepareGetRequestSession(Context context, + RequestSession.SessionLifetime sessionCallback, Object lock, int userId, + int callingUid, IGetCredentialCallback getCredCallback, GetCredentialRequest request, + CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long startedTimestamp, IPrepareGetCredentialCallback prepareGetCredentialCallback) { - super(context, userId, callingUid, callback, request, callingAppInfo, cancellationSignal, - startedTimestamp); + super(context, sessionCallback, lock, userId, callingUid, getCredCallback, request, + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( Collectors.toSet())).size(); // Dedupe type strings diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 1b736e01c842..0c3d3f4ed6d2 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -23,6 +23,7 @@ import android.credentials.ClearCredentialStateException; import android.credentials.CredentialProviderInfo; import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; +import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.ClearCredentialStateRequest; import android.util.Log; @@ -109,6 +110,11 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS } } + @Override + public void onProviderCancellable(ICancellationSignal cancellation) { + mProviderCancellationSignal = cancellation; + } + @Nullable @Override protected ProviderData prepareUiData() { diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index bef045f8f890..8b9255a9c56b 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -29,6 +29,7 @@ import android.credentials.ui.CreateCredentialProviderData; import android.credentials.ui.Entry; import android.credentials.ui.ProviderPendingIntentResponse; import android.os.Bundle; +import android.os.ICancellationSignal; import android.service.credentials.BeginCreateCredentialRequest; import android.service.credentials.BeginCreateCredentialResponse; import android.service.credentials.CallingAppInfo; @@ -173,6 +174,11 @@ public final class ProviderCreateSession extends ProviderSession< } } + @Override + public void onProviderCancellable(ICancellationSignal cancellation) { + mProviderCancellationSignal = cancellation; + } + private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) { Log.i(TAG, "onSetInitialRemoteResponse with save entries"); mProviderResponse = response; @@ -236,7 +242,7 @@ public final class ProviderCreateSession extends ProviderSession< protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mRemoteCredentialService.onCreateCredential(mProviderRequest, this); + mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 427a8945c573..8d3d06469944 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -30,6 +30,7 @@ import android.credentials.ui.AuthenticationEntry; import android.credentials.ui.Entry; import android.credentials.ui.GetCredentialProviderData; import android.credentials.ui.ProviderPendingIntentResponse; +import android.os.ICancellationSignal; import android.service.credentials.Action; import android.service.credentials.BeginGetCredentialOption; import android.service.credentials.BeginGetCredentialRequest; @@ -235,6 +236,11 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential } } + @Override + public void onProviderCancellable(ICancellationSignal cancellation) { + mProviderCancellationSignal = cancellation; + } + @Override // Selection call from the request provider protected void onUiEntrySelected(String entryType, String entryKey, ProviderPendingIntentResponse providerPendingIntentResponse) { diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 9cf27210dde0..24292ef2cdab 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -29,6 +29,7 @@ import android.credentials.ui.Entry; import android.credentials.ui.GetCredentialProviderData; import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; +import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderService; @@ -115,7 +116,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption @NonNull String servicePackageName, @NonNull CredentialOption requestOption) { super(context, requestOption, session, - new ComponentName(servicePackageName, servicePackageName) , + new ComponentName(servicePackageName, servicePackageName), userId, null); mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId); mCallingAppInfo = callingAppInfo; @@ -132,7 +133,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption @NonNull String servicePackageName, @NonNull CredentialOption requestOption) { super(context, requestOption, session, - new ComponentName(servicePackageName, servicePackageName) , + new ComponentName(servicePackageName, servicePackageName), userId, null); mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId); mCallingAppInfo = callingAppInfo; @@ -255,13 +256,18 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption } @Override + public void onProviderCancellable(ICancellationSignal cancellation) { + // No need to do anything since this class does not rely on a remote service. + } + + @Override protected void invokeSession() { mProviderResponse = mCredentialDescriptionRegistry .getFilteredResultForProvider(mCredentialProviderPackageName, mElementKeys); mCredentialEntries = mProviderResponse.stream().flatMap( - (Function<CredentialDescriptionRegistry.FilterResult, - Stream<CredentialEntry>>) filterResult + (Function<CredentialDescriptionRegistry.FilterResult, + Stream<CredentialEntry>>) filterResult -> filterResult.mCredentialEntries.stream()) .collect(Collectors.toList()); updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index 8c0e1c1511e6..d165756b3811 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -30,6 +30,7 @@ import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.os.RemoteException; import android.util.Log; +import android.util.Slog; import com.android.server.credentials.metrics.ProviderSessionMetric; @@ -189,7 +190,7 @@ public abstract class ProviderSession<T, R> } setStatus(Status.CANCELED); } catch (RemoteException e) { - Log.i(TAG, "Issue while cancelling provider session: " + e.getMessage()); + Slog.e(TAG, "Issue while cancelling provider session: ", e); } } diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index ff4e3b680131..c1e9bc6a5c3c 100644 --- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java +++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java @@ -82,6 +82,9 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr /** Called when the remote provider service dies. */ void onProviderServiceDied(RemoteCredentialService service); + + /** Called to set the cancellation transport from the remote provider service. */ + void onProviderCancellable(ICancellationSignal cancellation); } public RemoteCredentialService(@NonNull Context context, @@ -117,43 +120,56 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderGetSession} class that maintains provider state */ - public ICancellationSignal onBeginGetCredential(@NonNull BeginGetCredentialRequest request, + public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, ProviderCallbacks<BeginGetCredentialResponse> callback) { Log.i(TAG, "In onGetCredentials in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); + AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef = + new AtomicReference<>(); + CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> { CompletableFuture<BeginGetCredentialResponse> getCredentials = new CompletableFuture<>(); final long originalCallingUidToken = Binder.clearCallingIdentity(); try { - ICancellationSignal cancellationSignal = - service.onBeginGetCredential(request, - new IBeginGetCredentialCallback.Stub() { - @Override - public void onSuccess(BeginGetCredentialResponse response) { - getCredentials.complete(response); - } + service.onBeginGetCredential(request, + new IBeginGetCredentialCallback.Stub() { + @Override + public void onSuccess(BeginGetCredentialResponse response) { + getCredentials.complete(response); + } - @Override - public void onFailure(String errorType, CharSequence message) { - Log.i(TAG, "In onFailure in RemoteCredentialService"); - String errorMsg = message == null ? "" : String.valueOf( - message); - getCredentials.completeExceptionally( - new GetCredentialException(errorType, errorMsg)); - } - }); - cancellationSink.set(cancellationSignal); + @Override + public void onFailure(String errorType, CharSequence message) { + Log.i(TAG, "In onFailure in RemoteCredentialService"); + String errorMsg = message == null ? "" : String.valueOf( + message); + getCredentials.completeExceptionally( + new GetCredentialException(errorType, errorMsg)); + } + + @Override + public void onCancellable(ICancellationSignal cancellation) { + CompletableFuture<BeginGetCredentialResponse> future = + futureRef.get(); + if (future != null && future.isCancelled()) { + dispatchCancellationSignal(cancellation); + } else { + cancellationSink.set(cancellation); + callback.onProviderCancellable(cancellation); + } + } + }); return getCredentials; } finally { Binder.restoreCallingIdentity(originalCallingUidToken); } }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS); + futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); - return cancellationSink.get(); } /** @@ -164,10 +180,12 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderCreateSession} class that maintains provider state */ - public ICancellationSignal onCreateCredential(@NonNull BeginCreateCredentialRequest request, + public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, ProviderCallbacks<BeginCreateCredentialResponse> callback) { Log.i(TAG, "In onCreateCredential in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); + AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef = + new AtomicReference<>(); CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> { @@ -175,7 +193,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr new CompletableFuture<>(); final long originalCallingUidToken = Binder.clearCallingIdentity(); try { - ICancellationSignal cancellationSignal = service.onBeginCreateCredential( + service.onBeginCreateCredential( request, new IBeginCreateCredentialCallback.Stub() { @Override public void onSuccess(BeginCreateCredentialResponse response) { @@ -192,18 +210,28 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr createCredentialFuture.completeExceptionally( new CreateCredentialException(errorType, errorMsg)); } + + @Override + public void onCancellable(ICancellationSignal cancellation) { + CompletableFuture<BeginCreateCredentialResponse> future = + futureRef.get(); + if (future != null && future.isCancelled()) { + dispatchCancellationSignal(cancellation); + } else { + cancellationSink.set(cancellation); + callback.onProviderCancellable(cancellation); + } + } }); - cancellationSink.set(cancellationSignal); return createCredentialFuture; } finally { Binder.restoreCallingIdentity(originalCallingUidToken); } }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS); + futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); - - return cancellationSink.get(); } /** @@ -214,10 +242,11 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr * @param callback the callback to be used to send back the provider response to the * {@link ProviderClearSession} class that maintains provider state */ - public ICancellationSignal onClearCredentialState(@NonNull ClearCredentialStateRequest request, + public void onClearCredentialState(@NonNull ClearCredentialStateRequest request, ProviderCallbacks<Void> callback) { Log.i(TAG, "In onClearCredentialState in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); + AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>(); CompletableFuture<Void> connectThenExecute = postAsync(service -> { @@ -225,7 +254,7 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr new CompletableFuture<>(); final long originalCallingUidToken = Binder.clearCallingIdentity(); try { - ICancellationSignal cancellationSignal = service.onClearCredentialState( + service.onClearCredentialState( request, new IClearCredentialStateCallback.Stub() { @Override public void onSuccess() { @@ -243,18 +272,27 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr new ClearCredentialStateException(errorType, errorMsg)); } + + @Override + public void onCancellable(ICancellationSignal cancellation) { + CompletableFuture<Void> future = futureRef.get(); + if (future != null && future.isCancelled()) { + dispatchCancellationSignal(cancellation); + } else { + cancellationSink.set(cancellation); + callback.onProviderCancellable(cancellation); + } + } }); - cancellationSink.set(cancellationSignal); return clearCredentialFuture; } finally { Binder.restoreCallingIdentity(originalCallingUidToken); } }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS); + futureRef.set(connectThenExecute); connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> handleExecutionResponse(result, error, cancellationSink, callback))); - - return cancellationSink.get(); } private <T> void handleExecutionResponse(T result, diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 04c4bc4c7c4f..e98c5241ae00 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.credentials.CredentialProviderInfo; import android.credentials.ui.ProviderData; import android.credentials.ui.UserSelectionDialogResult; @@ -29,8 +30,10 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.service.credentials.CallingAppInfo; import android.util.Log; +import android.util.Slog; import com.android.internal.R; import com.android.server.credentials.metrics.ApiName; @@ -39,8 +42,9 @@ import com.android.server.credentials.metrics.ProviderStatusForMetrics; import com.android.server.credentials.metrics.RequestSessionMetric; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Base class of a request session, that listens to UI events. This class must be extended @@ -49,6 +53,11 @@ import java.util.Map; abstract class RequestSession<T, U, V> implements CredentialManagerUi.CredentialManagerUiCallback { private static final String TAG = "RequestSession"; + public interface SessionLifetime { + /** Called when the user makes a selection. */ + void onFinishRequestSession(@UserIdInt int userId, IBinder token); + } + // TODO: Revise access levels of attributes @NonNull protected final T mClientRequest; @@ -72,10 +81,16 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential @NonNull protected final CancellationSignal mCancellationSignal; - protected final Map<String, ProviderSession> mProviders = new HashMap<>(); + protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>(); protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric(); protected final String mHybridService; + protected final Object mLock; + + protected final SessionLifetime mSessionCallback; + + private final Set<ComponentName> mEnabledProviders; + @NonNull protected RequestSessionStatus mRequestSessionStatus = RequestSessionStatus.IN_PROGRESS; @@ -91,26 +106,58 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } protected RequestSession(@NonNull Context context, - @UserIdInt int userId, int callingUid, @NonNull T clientRequest, U clientCallback, + RequestSession.SessionLifetime sessionCallback, + Object lock, @UserIdInt int userId, int callingUid, + @NonNull T clientRequest, U clientCallback, @NonNull String requestType, CallingAppInfo callingAppInfo, + Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long timestampStarted) { mContext = context; + mLock = lock; + mSessionCallback = sessionCallback; mUserId = userId; mCallingUid = callingUid; mClientRequest = clientRequest; mClientCallback = clientCallback; mRequestType = requestType; mClientAppInfo = callingAppInfo; + mEnabledProviders = enabledProviders; mCancellationSignal = cancellationSignal; mHandler = new Handler(Looper.getMainLooper(), null, true); mRequestId = new Binder(); mCredentialManagerUi = new CredentialManagerUi(mContext, - mUserId, this); + mUserId, this, mEnabledProviders); mHybridService = context.getResources().getString( R.string.config_defaultCredentialManagerHybridService); mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId, mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType)); + setCancellationListener(); + } + + private void setCancellationListener() { + mCancellationSignal.setOnCancelListener( + () -> { + boolean isUiActive = maybeCancelUi(); + finishSession(!isUiActive); + } + ); + } + + private boolean maybeCancelUi() { + if (mCredentialManagerUi.getStatus() + == CredentialManagerUi.UiStatus.USER_INTERACTION) { + final long originalCallingUidToken = Binder.clearCallingIdentity(); + try { + mContext.startActivityAsUser(mCredentialManagerUi.createCancelIntent( + mRequestId, mClientAppInfo.getPackageName()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), UserHandle.of(mUserId)); + return true; + } finally { + Binder.restoreCallingIdentity(originalCallingUidToken); + } + } + return false; } public abstract ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo, @@ -154,12 +201,19 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } protected void finishSession(boolean propagateCancellation) { - Log.i(TAG, "finishing session"); + Slog.d(TAG, "finishing session with propagateCancellation " + propagateCancellation); if (propagateCancellation) { mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); } mRequestSessionStatus = RequestSessionStatus.COMPLETE; mProviders.clear(); + clearRequestSessionLocked(); + } + + private void clearRequestSessionLocked() { + synchronized (mLock) { + mSessionCallback.onFinishRequestSession(mUserId, mRequestId); + } } protected boolean isAnyProviderPending() { @@ -257,7 +311,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential * Allows subclasses to directly finalize the call and set closing metrics on error completion. * * @param errorType the type of error given back in the flow - * @param errorMsg the error message given back in the flow + * @param errorMsg the error message given back in the flow */ protected void respondToClientWithErrorAndFinish(String errorType, String errorMsg) { mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index a109dee73fe8..d55099f604a2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -92,6 +92,8 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION; import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS; import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; +import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK; +import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY; @@ -439,6 +441,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; @@ -560,6 +563,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int REQUEST_PROFILE_OFF_DEADLINE = 5572; + private static final int MAX_PROFILE_NAME_LENGTH = 200; + private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1); private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms @@ -773,6 +778,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public static final long EXPLICIT_WIPE_BEHAVIOUR = 242193913L; + /** + * Apps targetting U+ should now expect that attempts to grant sensor permissions without + * authorisation will result in a security exception. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final long THROW_SECURITY_EXCEPTION_FOR_SENSOR_PERMISSIONS = 277035314L; + // Only add to the end of the list. Do not change or rearrange these values, that will break // historical data. Do not use negative numbers or zero, logger only handles positive // integers. @@ -2819,6 +2832,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return doAdmin; } + ActiveAdmin getDefaultDeviceOwnerLocked(@UserIdInt int userId) { + ensureLocked(); + ComponentName doComponent = mOwners.getDeviceOwnerComponent(); + if (mOwners.getDeviceOwnerType(doComponent.getPackageName()) == DEFAULT_DEVICE_OWNER) { + ActiveAdmin doAdmin = getUserData(userId).mAdminMap.get(doComponent); + return doAdmin; + } + return null; + } + ActiveAdmin getProfileOwnerLocked(@UserIdInt int userId) { ensureLocked(); final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId); @@ -2848,6 +2871,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return getDeviceOwnerLocked(userId); } + ActiveAdmin getProfileOwnerOrDefaultDeviceOwnerLocked(@UserIdInt int userId) { + ensureLocked(); + // Try to find an admin which can use reqPolicy + final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(userId); + + if (poAdminComponent != null) { + return getProfileOwnerLocked(userId); + } + + return getDefaultDeviceOwnerLocked(userId); + } + @NonNull ActiveAdmin getParentOfAdminIfRequired(ActiveAdmin admin, boolean parent) { Objects.requireNonNull(admin); return parent ? admin.getParentActiveAdmin() : admin; @@ -5337,9 +5372,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { saveSettingsLocked(caller.getUserId()); }); + + //TODO(b/276855301): caller.getPackageName() will be null when the coexistence flags are + // turned off. Change back to caller.getPackageName once this API is unflagged. DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_PASSWORD_COMPLEXITY) - .setAdmin(caller.getPackageName()) + .setAdmin(admin.info.getPackageName()) .setInt(passwordComplexity) .setBoolean(calledOnParent) .write(); @@ -5974,8 +6012,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void lockNow(int flags, boolean parent) { - final CallerIdentity caller = getCallerIdentity(); + public void lockNow(int flags, String callerPackageName, boolean parent) { + CallerIdentity caller; + if (isPermissionCheckFlagEnabled()) { + caller = getCallerIdentity(callerPackageName); + } else { + caller = getCallerIdentity(); + } final int callingUserId = caller.getUserId(); ComponentName adminComponent = null; @@ -5984,11 +6027,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Make sure the caller has any active admin with the right policy or // the required permission. if (isPermissionCheckFlagEnabled()) { - admin = getActiveAdminOrCheckPermissionsForCallerLocked( - null, - DeviceAdminInfo.USES_POLICY_FORCE_LOCK, - parent, - Set.of(MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE)); + admin = enforcePermissionAndGetEnforcingAdmin( + /* admin= */ null, + /* permission= */ MANAGE_DEVICE_POLICY_LOCK, + USES_POLICY_FORCE_LOCK, + caller.getPackageName(), + getAffectedUser(parent) + ).getActiveAdmin(); } else { admin = getActiveAdminOrCheckPermissionForCallerLocked( null, @@ -7481,7 +7526,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isPolicyEngineForFinanceFlagEnabled()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( /*admin=*/ null, - MANAGE_DEVICE_POLICY_WIPE_DATA, + /*permission= */ MANAGE_DEVICE_POLICY_WIPE_DATA, + USES_POLICY_WIPE_DATA, caller.getPackageName(), factoryReset ? UserHandle.USER_ALL : getAffectedUser(calledOnParentInstance)); admin = enforcingAdmin.getActiveAdmin(); @@ -8511,7 +8557,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isProfileOwnerOfOrganizationOwnedDevice(caller)); } else { Preconditions.checkCallAuthorization(isProfileOwner(caller) - || isDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } } @@ -8525,7 +8571,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { targetUserId).getActiveAdmin(); } else { ap = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); + getProfileOwnerOrDefaultDeviceOwnerLocked(caller.getUserId()), parent); } if (ap.disableScreenCapture != disabled) { @@ -8557,15 +8603,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (admin != null && admin.disableScreenCapture) { setScreenCaptureDisabled(UserHandle.USER_ALL); - } else { - // Otherwise, update screen capture only for the calling user. - admin = getProfileOwnerAdminLocked(adminUserId); - if (admin != null && admin.disableScreenCapture) { - setScreenCaptureDisabled(adminUserId); - } else { - setScreenCaptureDisabled(UserHandle.USER_NULL); - } + return; } + // Otherwise, update screen capture only for the calling user. + admin = getProfileOwnerAdminLocked(adminUserId); + if (admin != null && admin.disableScreenCapture) { + setScreenCaptureDisabled(adminUserId); + return; + } + // If the admin is permission based, update only for the calling user. + admin = getUserData(adminUserId).createOrGetPermissionBasedAdmin(adminUserId); + if (admin != null && admin.disableScreenCapture) { + setScreenCaptureDisabled(adminUserId); + return; + } + setScreenCaptureDisabled(UserHandle.USER_NULL); } // Set the latest screen capture policy, overriding any existing ones. @@ -10330,8 +10382,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + final String truncatedProfileName = + profileName.substring(0, Math.min(profileName.length(), MAX_PROFILE_NAME_LENGTH)); mInjector.binderWithCleanCallingIdentity(() -> { - mUserManager.setUserName(caller.getUserId(), profileName); + mUserManager.setUserName(caller.getUserId(), truncatedProfileName); DevicePolicyEventLogger .createEvent(DevicePolicyEnums.SET_PROFILE_NAME) .setAdmin(caller.getComponentName()) @@ -11424,7 +11478,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { RoleManager.ROLE_DIALER, packageName, 0, UserHandle.of(callerUserId), AsyncTask.THREAD_POOL_EXECUTOR, callback); try { - future.get(5, TimeUnit.SECONDS); + future.get(20, TimeUnit.SECONDS); } catch (TimeoutException e) { throw new IllegalArgumentException("Timeout when setting the app as the dialer", e); } catch (ExecutionException e) { @@ -16466,6 +16520,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, callerPackage, caller.getUserId()); + if (SENSOR_PERMISSIONS.contains(permission) + && grantState == PERMISSION_GRANT_STATE_GRANTED + && (!canAdminGrantSensorsPermissions() || isCallerDelegate(caller))) { + if (mInjector.isChangeEnabled(THROW_SECURITY_EXCEPTION_FOR_SENSOR_PERMISSIONS, + caller.getPackageName(), caller.getUserId())) { + throw new SecurityException( + "Caller not permitted to grant sensor permissions."); + } else { + // This is to match the legacy behaviour. + callback.sendResult(Bundle.EMPTY); + return; + } + } + // Check all the states where Exceptions aren't thrown but the permission + // isn't granted either. + if (!canGrantPermission(caller, permission, packageName)) { + callback.sendResult(null); + return; + } // TODO(b/266924257): decide how to handle the internal state if the package doesn't // exist, or the permission isn't requested by the app, because we could end up with // inconsistent state between the policy engine and package manager. Also a package @@ -16541,6 +16614,41 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private static final List<String> SENSOR_PERMISSIONS = new ArrayList<>(); + { + SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION); + SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); + SENSOR_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION); + SENSOR_PERMISSIONS.add(Manifest.permission.CAMERA); + SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO); + SENSOR_PERMISSIONS.add(Manifest.permission.ACTIVITY_RECOGNITION); + SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); + SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA); + SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO); + SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); + SENSOR_PERMISSIONS.add( + Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE); + SENSOR_PERMISSIONS.add( + Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); + } + + private boolean canGrantPermission(CallerIdentity caller, String permission, + String targetPackageName) { + boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) + >= android.os.Build.VERSION_CODES.Q; + if (!isPostQAdmin) { + // Legacy admins assume that they cannot control pre-M apps + if (getTargetSdk(targetPackageName, caller.getUserId()) + < android.os.Build.VERSION_CODES.M) { + return false; + } + } + if (!isRuntimePermission(permission)) { + return false; + } + return true; + } + private void enforcePermissionGrantStateOnFinancedDevice( String packageName, String permission) { if (!Manifest.permission.READ_PHONE_STATE.equals(permission)) { @@ -17637,7 +17745,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { if (isPermissionCheckFlagEnabled()) { - // TODO: add support for DELEGATION_SECURITY_LOGGING enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -17718,7 +17825,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(admin, packageName); if (isPermissionCheckFlagEnabled()) { - // TODO: Restore the "affiliated users" check + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -17770,7 +17878,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(admin, packageName); if (isPermissionCheckFlagEnabled()) { - // TODO: Restore the "affiliated users" check + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); + enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -22321,244 +22431,208 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Permissions of existing DPC types. private static final List<String> DEFAULT_DEVICE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, + MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, - SET_TIME, - SET_TIME_ZONE, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_WIFI, - MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, - MANAGE_DEVICE_POLICY_MTE, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_CERTIFICATES, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, - MANAGE_DEVICE_POLICY_CAMERA, - MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - MANAGE_DEVICE_POLICY_DEFAULT_SMS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_STATUS_BAR, - MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, MANAGE_DEVICE_POLICY_AIRPLANE_MODE, + MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, MANAGE_DEVICE_POLICY_AUTOFILL, MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_CALLS, MANAGE_DEVICE_POLICY_CAMERA, + MANAGE_DEVICE_POLICY_CERTIFICATES, + MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, + MANAGE_DEVICE_POLICY_DEFAULT_SMS, MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_FACTORY_RESET, MANAGE_DEVICE_POLICY_FUN, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, + MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_LOCALE, MANAGE_DEVICE_POLICY_LOCATION, + MANAGE_DEVICE_POLICY_LOCK, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, + MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_MICROPHONE, MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_MTE, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + MANAGE_DEVICE_POLICY_PACKAGE_STATE, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PRINTING, - MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, MANAGE_DEVICE_POLICY_PROFILES, MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, + MANAGE_DEVICE_POLICY_RESET_PASSWORD, + MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, + MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, MANAGE_DEVICE_POLICY_SMS, + MANAGE_DEVICE_POLICY_STATUS_BAR, + MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, + MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, MANAGE_DEVICE_POLICY_TIME, + MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USERS, MANAGE_DEVICE_POLICY_VPN, MANAGE_DEVICE_POLICY_WALLPAPER, MANAGE_DEVICE_POLICY_WIFI, MANAGE_DEVICE_POLICY_WINDOWS, - MANAGE_DEVICE_POLICY_APP_RESTRICTIONS + MANAGE_DEVICE_POLICY_WIPE_DATA, + SET_TIME, + SET_TIME_ZONE ); private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_CALLS, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, + MANAGE_DEVICE_POLICY_FACTORY_RESET, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_USERS, + MANAGE_DEVICE_POLICY_KEYGUARD, + MANAGE_DEVICE_POLICY_LOCK_TASK, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); - private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = + MANAGE_DEVICE_POLICY_USERS, + MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS + ); + + /** + * All the permisisons granted to a profile owner. + */ + private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, - SET_TIME, - SET_TIME_ZONE, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_WIFI, - MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, - MANAGE_DEVICE_POLICY_MTE, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_AIRPLANE_MODE, - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, + MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, MANAGE_DEVICE_POLICY_AUTOFILL, - MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_CALLS, - MANAGE_DEVICE_POLICY_CAMERA, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_FACTORY_RESET, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, + MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_LOCALE, MANAGE_DEVICE_POLICY_LOCATION, + MANAGE_DEVICE_POLICY_LOCK, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_MICROPHONE, - MANAGE_DEVICE_POLICY_MOBILE_NETWORK, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, - MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + MANAGE_DEVICE_POLICY_PACKAGE_STATE, MANAGE_DEVICE_POLICY_PRINTING, MANAGE_DEVICE_POLICY_PROFILES, MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, - MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_RESET_PASSWORD, + MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, + MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT, - MANAGE_DEVICE_POLICY_SMS, + MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_TIME, MANAGE_DEVICE_POLICY_VPN, - MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - MANAGE_DEVICE_POLICY_DEFAULT_SMS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, - MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_WIFI, - MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, - MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, - MANAGE_DEVICE_POLICY_MTE, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_CERTIFICATES); - private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of( - SET_TIME, - SET_TIME_ZONE, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_AIRPLANE_MODE, - MANAGE_DEVICE_POLICY_BLUETOOTH, - MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_FUN, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_MOBILE_NETWORK, - MANAGE_DEVICE_POLICY_USERS, - MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, - MANAGE_DEVICE_POLICY_SAFE_BOOT, - MANAGE_DEVICE_POLICY_SMS, - MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_WINDOWS, - MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, - MANAGE_DEVICE_POLICY_CAMERA, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_STATUS_BAR, - MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, - MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS); - private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, - MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, - MANAGE_DEVICE_POLICY_AUTOFILL, - MANAGE_DEVICE_POLICY_CALLS, - MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, - MANAGE_DEVICE_POLICY_DISPLAY, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_LOCALE, - MANAGE_DEVICE_POLICY_LOCATION, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, - MANAGE_DEVICE_POLICY_PRINTING, - MANAGE_DEVICE_POLICY_PROFILES, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - MANAGE_DEVICE_POLICY_SCREEN_CONTENT, - MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, - MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_VPN, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_APP_RESTRICTIONS + MANAGE_DEVICE_POLICY_WIPE_DATA ); + /** + * All the additional permissions granted to an organisation owned profile owner. + */ + private static final List<String> + ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = + List.of( + MANAGE_DEVICE_POLICY_ACROSS_USERS, + MANAGE_DEVICE_POLICY_AIRPLANE_MODE, + MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_BLUETOOTH, + MANAGE_DEVICE_POLICY_CAMERA, + MANAGE_DEVICE_POLICY_CERTIFICATES, + MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, + MANAGE_DEVICE_POLICY_DEFAULT_SMS, + MANAGE_DEVICE_POLICY_LOCALE, + MANAGE_DEVICE_POLICY_MICROPHONE, + MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_MTE, + MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, + MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, + MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, + MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + MANAGE_DEVICE_POLICY_SMS, + MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, + MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, + MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, + MANAGE_DEVICE_POLICY_WIFI, + SET_TIME, + SET_TIME_ZONE + ); + + + private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS = + List.of( + MANAGE_DEVICE_POLICY_AIRPLANE_MODE, + MANAGE_DEVICE_POLICY_BLUETOOTH, + MANAGE_DEVICE_POLICY_CAMERA, + MANAGE_DEVICE_POLICY_DISPLAY, + MANAGE_DEVICE_POLICY_FUN, + MANAGE_DEVICE_POLICY_LOCK_TASK, + MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, + MANAGE_DEVICE_POLICY_PRINTING, + MANAGE_DEVICE_POLICY_PROFILES, + MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, + MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_SMS, + MANAGE_DEVICE_POLICY_STATUS_BAR, + MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, + MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, + MANAGE_DEVICE_POLICY_USERS, + MANAGE_DEVICE_POLICY_WINDOWS, + SET_TIME, + SET_TIME_ZONE + ); + + /** + * Combination of {@link PROFILE_OWNER_PERMISSIONS} and + * {@link ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS}. + */ + private static final List<String> PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = + new ArrayList(); + + /** + * Combination of {@link PROFILE_OWNER_PERMISSIONS} and + * {@link ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS}. + */ + private static final List<String> PROFILE_OWNER_ON_USER_0_PERMISSIONS = + new ArrayList(); + + private static final HashMap<Integer, List<String>> DPC_PERMISSIONS = new HashMap<>(); { + // Organisation owned profile owners have all the permission of a profile owner plus + // some extra permissions. + PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS.addAll(PROFILE_OWNER_PERMISSIONS); + PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS.addAll( + ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS); + // Profile owners on user 0 have all the permission of a profile owner plus + // some extra permissions. + PROFILE_OWNER_ON_USER_0_PERMISSIONS.addAll(PROFILE_OWNER_PERMISSIONS); + PROFILE_OWNER_ON_USER_0_PERMISSIONS.addAll(ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS); + DPC_PERMISSIONS.put(DEFAULT_DEVICE_OWNER, DEFAULT_DEVICE_OWNER_PERMISSIONS); DPC_PERMISSIONS.put(FINANCED_DEVICE_OWNER, FINANCED_DEVICE_OWNER_PERMISSIONS); DPC_PERMISSIONS.put(PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE, @@ -22566,14 +22640,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DPC_PERMISSIONS.put(PROFILE_OWNER_ON_USER_0, PROFILE_OWNER_ON_USER_0_PERMISSIONS); DPC_PERMISSIONS.put(PROFILE_OWNER, PROFILE_OWNER_PERMISSIONS); } - - // Map of permission Active admin DEVICE_POLICY. - //TODO(b/254253251) Fill this map in as new permissions are added for policies. - private static final HashMap<String, Integer> ACTIVE_ADMIN_POLICIES = new HashMap<>(); - { - //Any ActiveAdmin is able to call the support message APIs without certain policies. - ACTIVE_ADMIN_POLICIES.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, null); - } //Map of Permission to Delegate Scope. private static final HashMap<String, String> DELEGATE_SCOPES = new HashMap<>(); { @@ -22587,123 +22653,119 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final HashMap<String, String> CROSS_USER_PERMISSIONS = new HashMap<>(); { - // Time and Timezone is intrinsically global so there is no cross-user permission. - CROSS_USER_PERMISSIONS.put(SET_TIME, null); - CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null); - // system updates are intrinsically global so there is no cross-user permission - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, null); - // security logs are intrinsically global so there is no cross-user permission - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, null); - // usb signalling is intrinsically global so there is no cross-user permission - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, null); - // mte is intrinsically global so there is no cross-user permission - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MTE, null); + // The permissions are all intrinsically global and therefore have no cross-user permission. CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FACTORY_RESET, null); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MTE, null); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, null); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_STATUS_BAR, null); - // Organisation identity policy will involve data of other organisations on the device and - // therefore the FULL cross-user permission is required. - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, null); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, null); + CROSS_USER_PERMISSIONS.put(SET_TIME, null); + CROSS_USER_PERMISSIONS.put(SET_TIME_ZONE, null); + + // The permissions are all critical for securing data within the current user and + // therefore are protected with MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL for + // cross-user calls. + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_KEYGUARD, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put( - MANAGE_DEVICE_POLICY_LOCK, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put( - MANAGE_DEVICE_POLICY_KEYGUARD, MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); - // Granting runtime permissions can grant applications significant powers therefore the FULL - // cross-user permission is required. - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + + // These permissions are required for securing device ownership without accessing user data + // and therefore are protected with MANAGE_DEVICE_POLICY_ACROSS_USERS for cross-user calls. CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CALLS, MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA, MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SMS, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SAFE_BOOT, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_TIME, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIPE_DATA, + MANAGE_DEVICE_POLICY_ACROSS_USERS); + + // These permissions may grant access to user data and therefore must be protected with + // MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL for cross-user calls. + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_FUN, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCALE, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCATION, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MOBILE_NETWORK, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PRINTING, + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS, - MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SAFE_BOOT, - MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PRINTING, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESET_PASSWORD, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SCREEN_CONTENT, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SMS, - MANAGE_DEVICE_POLICY_ACROSS_USERS); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER, - MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WIFI, - MANAGE_DEVICE_POLICY_ACROSS_USERS); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WINDOWS, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEFAULT_SMS, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_ACROSS_USERS); - CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); } /** @@ -22727,6 +22789,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. + * Returns an {@link EnforcingAdmin} for the caller. + * + * @param admin the component name of the admin. + * @param callerPackageName The package name of the calling application. + * @param permission The name of the permission being checked. + * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin, + String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) { + enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId); + return getEnforcingAdminForCaller(admin, callerPackageName); + } + + /** * Checks whether the calling process has been granted permission to query a device policy on * a specific user. * The given permission will be checked along with its associated cross-user permission if it @@ -22748,6 +22830,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { POLICY_IDENTIFIER_TO_PERMISSION.put(AUTO_TIMEZONE_POLICY, SET_TIME_ZONE); } + private static final HashMap<String, Integer> POLICY_IDENTIFIER_TO_ACTIVE_ADMIN_POLICY = + new HashMap<>(); + /** * Checks if the calling process has been granted permission to apply a device policy on a * specific user. @@ -22763,6 +22848,36 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void enforcePermission(String permission, String callerPackageName, int targetUserId) throws SecurityException { if (!hasPermission(permission, callerPackageName, targetUserId)) { + // TODO(b/276920002): Split the error messages so that the cross-user permission + // is only mentioned when it is needed. + throw new SecurityException("Caller does not have the required permissions for " + + "this user. Permissions required: {" + + permission + + ", " + + CROSS_USER_PERMISSIONS.get(permission) + + "(if calling cross-user)" + + "}"); + } + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. + * The given permission will be checked along with its associated cross-user permission if it + * exists and the target user is different to the calling user. + * + * @param callerPackageName The package name of the calling application. + * @param permission The name of the permission being checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermission(String permission, int adminPolicy, + String callerPackageName, int targetUserId) + throws SecurityException { + if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) { + // TODO(b/276920002): Split the error messages so that the cross-user permission + // is only mentioned when it is needed. throw new SecurityException("Caller does not have the required permissions for " + "this user. Permissions required: {" + permission @@ -22804,16 +22919,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private boolean hasPermission(String permission, String callerPackageName, int targetUserId) { CallerIdentity caller = getCallerIdentity(callerPackageName); - boolean hasPermissionOnOwnUser = hasPermission(permission, callerPackageName); + boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName()); boolean hasPermissionOnTargetUser = true; if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) { hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission), - callerPackageName); + caller.getPackageName()); } return hasPermissionOnOwnUser && hasPermissionOnTargetUser; } + private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName, + int adminPolicy, int targetUserId) { + CallerIdentity caller = getCallerIdentity(callerPackageName); + if (hasPermission(permission, caller.getPackageName(), targetUserId)) { + return true; + } + ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller); + return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy); + } + /** * Return whether the calling process has been granted the given permission. * @@ -22856,23 +22981,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (DELEGATE_SCOPES.containsKey(permission)) { return isCallerDelegate(caller, DELEGATE_SCOPES.get(permission)); } - // Check if the caller is an active admin that uses a certain policy. - if (ACTIVE_ADMIN_POLICIES.containsKey(permission)) { - try { - if (ACTIVE_ADMIN_POLICIES.get(permission) != null) { - return getActiveAdminForCallerLocked( - null, ACTIVE_ADMIN_POLICIES.get(permission), false) != null; - } else { - // If the permission maps to no policy (null) this means that any active admin - // has permission. - return isCallerActiveAdminOrDelegate(caller, null); - } - } catch (SecurityException e) { - // A security exception means there is not an active admin with permission and - // therefore - return false; - } - } return false; } @@ -22925,9 +23033,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (admin != null) { return EnforcingAdmin.createDeviceAdminEnforcingAdmin(who, userId, admin); } - if (admin == null) { - admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId); - } + admin = getUserData(userId).createOrGetPermissionBasedAdmin(userId); return EnforcingAdmin.createEnforcingAdmin(caller.getPackageName(), userId, admin); } @@ -23476,15 +23582,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // We need to add a mapping of policyId to permission in POLICY_IDENTIFIER_TO_PERMISSION // for each migrated permission. private List<ActiveAdmin> getNonDPCActiveAdminsForPolicyLocked(String policyIdentifier) { - String permission = POLICY_IDENTIFIER_TO_PERMISSION.get(policyIdentifier); - if (permission == null) { - Slogf.e(LOG_TAG, "Can't find a permission for %s in POLICY_IDENTIFIER_TO_PERMISSION", + Integer activeAdminPolicy = POLICY_IDENTIFIER_TO_ACTIVE_ADMIN_POLICY.get(policyIdentifier); + if (activeAdminPolicy == null) { + Slogf.e(LOG_TAG, + "Can't find a active admin policy for %s in POLICY_IDENTIFIER_TO_PERMISSION", policyIdentifier); return new ArrayList<>(); } - if (!ACTIVE_ADMIN_POLICIES.containsKey(permission)) { - return new ArrayList<>(); - } List<ActiveAdmin> admins = new ArrayList<>(); for (UserInfo userInfo : mUserManager.getUsers()) { @@ -23495,7 +23599,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } DevicePolicyData policy = getUserDataUnchecked(userInfo.id); if (isActiveAdminWithPolicyForUserLocked( - policy.mAdminMap.get(admin), ACTIVE_ADMIN_POLICIES.get(permission), + policy.mAdminMap.get(admin), activeAdminPolicy, userInfo.id)) { admins.add(policy.mAdminMap.get(admin)); } 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 0c512d2a0bd3..318067ee8681 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -91,8 +91,8 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import java.io.ByteArrayOutputStream; import java.io.PrintWriter; +import java.io.Writer; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; @@ -596,7 +596,7 @@ public final class BroadcastQueueModernImplTest { // about the actual output, just that we don't crash queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED, "Test-driven"); queue.dumpLocked(SystemClock.uptimeMillis(), - new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream()))); + new IndentingPrintWriter(new PrintWriter(Writer.nullWriter()))); queue.makeActiveNextPending(); assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); @@ -1166,6 +1166,11 @@ public final class BroadcastQueueModernImplTest { List<Intent> intents) { for (int i = 0; i < intents.size(); i++) { queue.makeActiveNextPending(); + + // While we're here, give our health check some test coverage + queue.assertHealthLocked(); + queue.dumpLocked(0L, new IndentingPrintWriter(Writer.nullWriter())); + final Intent actualIntent = queue.getActive().intent; final Intent expectedIntent = intents.get(i); final String errMsg = "actual=" + actualIntent + ", expected=" + expectedIntent 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 cbc259797c12..b6bc02a41c21 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -39,6 +39,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -106,10 +107,10 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -231,6 +232,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting startProcessLocked() for " + Arrays.toString(invocation.getArguments())); + assertHealth(); final ProcessStartBehavior behavior = mNextProcessStartBehavior .getAndSet(ProcessStartBehavior.SUCCESS); if (behavior == ProcessStartBehavior.FAIL_NULL) { @@ -462,6 +464,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleReceiver() for " + Arrays.toString(invocation.getArguments())); + assertHealth(); final Intent intent = invocation.getArgument(0); final Bundle extras = invocation.getArgument(5); mScheduledBroadcasts.add(makeScheduledBroadcast(r, intent)); @@ -483,6 +486,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " + Arrays.toString(invocation.getArguments())); + assertHealth(); final Intent intent = invocation.getArgument(1); final Bundle extras = invocation.getArgument(4); final boolean ordered = invocation.getArgument(5); @@ -600,6 +604,13 @@ public class BroadcastQueueTest { BackgroundStartPrivileges.NONE, false, null); } + private void assertHealth() { + if (mImpl == Impl.MODERN) { + // If this fails, it'll throw a clear reason message + ((BroadcastQueueModernImpl) mQueue).assertHealthLocked(); + } + } + private static Map<String, Object> asMap(Bundle bundle) { final Map<String, Object> map = new HashMap<>(); if (bundle != null) { @@ -769,7 +780,7 @@ public class BroadcastQueueTest { // about the actual output, just that we don't crash mQueue.dumpDebug(new ProtoOutputStream(), ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE); - mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()), + mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(Writer.nullWriter()), null, 0, true, true, true, null, false); mQueue.dumpToDropBoxLocked(TAG); @@ -1166,7 +1177,7 @@ public class BroadcastQueueTest { // about the actual output, just that we don't crash mQueue.dumpDebug(new ProtoOutputStream(), ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE); - mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(new ByteArrayOutputStream()), + mQueue.dumpLocked(FileDescriptor.err, new PrintWriter(Writer.nullWriter()), null, 0, true, true, true, null, false); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index fc503b7a749b..45fefe477f8a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -804,7 +804,7 @@ public final class DisplayPowerController2Test { when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); - when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( + when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn( DisplayDeviceConfig.DEFAULT_ID); when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index c021ef65a291..d9133a441aff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -809,7 +809,7 @@ public final class DisplayPowerControllerTest { when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); - when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( + when(logicalDisplayMock.getThermalBrightnessThrottlingDataIdLocked()).thenReturn( DisplayDeviceConfig.DEFAULT_ID); when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java index 03f667f0d336..df2f59a1cefc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java @@ -23,6 +23,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -39,6 +41,7 @@ import android.graphics.drawable.Icon; import android.os.UserHandle; import com.android.server.LocalServices; +import com.android.server.job.controllers.JobStatus; import com.android.server.notification.NotificationManagerInternal; import org.junit.After; @@ -493,6 +496,44 @@ public class JobNotificationCoordinatorTest { eq(notificationId), eq(UserHandle.getUserId(uid))); } + @Test + public void testUserInitiatedJob_hasNotificationFlag() { + final JobNotificationCoordinator coordinator = new JobNotificationCoordinator(); + final JobServiceContext jsc = mock(JobServiceContext.class); + final JobStatus js = mock(JobStatus.class); + js.startedAsUserInitiatedJob = true; + doReturn(js).when(jsc).getRunningJobLocked(); + final Notification notification = createValidNotification(); + final int uid = 10123; + final int pid = 42; + final int notificationId = 23; + + coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification, + JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); + verify(mNotificationManagerInternal) + .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), + eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid))); + assertNotEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0); + } + + @Test + public void testNonUserInitiatedJob_doesNotHaveNotificationFlag() { + final JobNotificationCoordinator coordinator = new JobNotificationCoordinator(); + final JobServiceContext jsc = mock(JobServiceContext.class); + doReturn(mock(JobStatus.class)).when(jsc).getRunningJobLocked(); + final Notification notification = createValidNotification(); + final int uid = 10123; + final int pid = 42; + final int notificationId = 23; + + coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification, + JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); + verify(mNotificationManagerInternal) + .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), + eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid))); + assertEquals(notification.flags & Notification.FLAG_USER_INITIATED_JOB, 0); + } + private Notification createValidNotification() { final Notification notification = mock(Notification.class); doReturn(mock(Icon.class)).when(notification).getSmallIcon(); diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index fc251526476e..3bef413a3e72 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -126,7 +126,7 @@ public class AutomaticBrightnessControllerTest { return mClock::now; } - }, // pass in test looper instead, pass in offsetable clock + }, // pass in test looper instead, pass in offsettable clock () -> { }, mTestLooper.getLooper(), mSensorManager, lightSensor, mBrightnessMappingStrategy, LIGHT_SENSOR_WARMUP_TIME, BRIGHTNESS_MIN_FLOAT, BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE, @@ -300,6 +300,179 @@ public class AutomaticBrightnessControllerTest { } @Test + public void testShortTermModelTimesOut() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + // User sets brightness to 100 + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, + /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, + /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, + /* shouldResetShortTermModel= */ true); + + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + when(mBrightnessMappingStrategy.shouldResetShortTermModel( + 123f, 0.5f)).thenReturn(true); + + // Sensor reads 1000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); + mTestLooper.moveTimeForward( + mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); + mTestLooper.dispatchAll(); + + mController.switchToInteractiveScreenBrightnessMode(); + mTestLooper.moveTimeForward(4000); + mTestLooper.dispatchAll(); + + // Verify only happens on the first configure. (i.e. not again when switching back) + // Intentionally using any() to ensure it's not called whatsoever. + verify(mBrightnessMappingStrategy, times(1)) + .addUserDataPoint(123.0f, 0.5f); + verify(mBrightnessMappingStrategy, times(1)) + .addUserDataPoint(anyFloat(), anyFloat()); + } + + @Test + public void testShortTermModelDoesntTimeOut() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + // User sets brightness to 100 + mController.configure(AUTO_BRIGHTNESS_ENABLED, null /* configuration= */, + 0.51f /* brightness= */, true /* userChangedBrightness= */, 0 /* adjustment= */, + false /* userChanged= */, DisplayPowerRequest.POLICY_BRIGHT, + /* shouldResetShortTermModel= */ true); + + when(mBrightnessMappingStrategy.shouldResetShortTermModel( + anyFloat(), anyFloat())).thenReturn(true); + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.51f); + when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123.0f); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + + // Time does not move forward, since clock is doesn't increment naturally. + mTestLooper.dispatchAll(); + + // Sensor reads 100000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 678910)); + mController.switchToInteractiveScreenBrightnessMode(); + + // Verify short term model is not reset. + verify(mBrightnessMappingStrategy, never()).clearUserDataPoints(); + + // Verify that we add the data point once when the user sets it, and again when we return + // interactive mode. + verify(mBrightnessMappingStrategy, times(2)) + .addUserDataPoint(123.0f, 0.51f); + } + + @Test + public void testShortTermModelIsRestoredWhenSwitchingWithinTimeout() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + // User sets brightness to 100 + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, + /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, + /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, + /* shouldResetShortTermModel= */ true); + + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f); + when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f); + when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f); + when(mBrightnessMappingStrategy.shouldResetShortTermModel( + 123f, 0.5f)).thenReturn(true); + + // Sensor reads 1000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); + mTestLooper.moveTimeForward( + mBrightnessMappingStrategy.getShortTermModelTimeout() + 1000); + mTestLooper.dispatchAll(); + + mController.switchToInteractiveScreenBrightnessMode(); + mTestLooper.moveTimeForward(4000); + mTestLooper.dispatchAll(); + + // Verify only happens on the first configure. (i.e. not again when switching back) + // Intentionally using any() to ensure it's not called whatsoever. + verify(mBrightnessMappingStrategy, times(1)) + .addUserDataPoint(123.0f, 0.5f); + verify(mBrightnessMappingStrategy, times(1)) + .addUserDataPoint(anyFloat(), anyFloat()); + } + + @Test + public void testShortTermModelNotRestoredAfterTimeout() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Sensor reads 123 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 123)); + // User sets brightness to 100 + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, + /* brightness= */ 0.5f, /* userChangedBrightness= */ true, /* adjustment= */ 0, + /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, + /* shouldResetShortTermModel= */ true); + + when(mBrightnessMappingStrategy.getShortTermModelTimeout()).thenReturn(2000L); + + when(mBrightnessMappingStrategy.getUserBrightness()).thenReturn(0.5f); + when(mBrightnessMappingStrategy.getUserLux()).thenReturn(123f); + + mController.switchToIdleMode(); + when(mIdleBrightnessMappingStrategy.isForIdleMode()).thenReturn(true); + when(mIdleBrightnessMappingStrategy.getUserBrightness()).thenReturn(-1f); + when(mIdleBrightnessMappingStrategy.getUserLux()).thenReturn(-1f); + + when(mBrightnessMappingStrategy.shouldResetShortTermModel( + 123f, 0.5f)).thenReturn(true); + + // Sensor reads 1000 lux, + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 1000)); + // Do not fast-forward time. + mTestLooper.dispatchAll(); + + mController.switchToInteractiveScreenBrightnessMode(); + // Do not fast-forward time + mTestLooper.dispatchAll(); + + // Verify this happens on the first configure and again when switching back + // Intentionally using any() to ensure it's not called any other times whatsoever. + verify(mBrightnessMappingStrategy, times(2)) + .addUserDataPoint(123.0f, 0.5f); + verify(mBrightnessMappingStrategy, times(2)) + .addUserDataPoint(anyFloat(), anyFloat()); + } + + @Test public void testSwitchToIdleMappingStrategy() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); @@ -326,6 +499,11 @@ public class AutomaticBrightnessControllerTest { // Called once for init, and once when switching, // setAmbientLux() is called twice and once in updateAutoBrightness() verify(mBrightnessMappingStrategy, times(5)).isForIdleMode(); + // Called when switching. + verify(mBrightnessMappingStrategy, times(1)).getShortTermModelTimeout(); + verify(mBrightnessMappingStrategy, times(1)).getUserBrightness(); + verify(mBrightnessMappingStrategy, times(1)).getUserLux(); + // Ensure, after switching, original BMS is not used anymore verifyNoMoreInteractions(mBrightnessMappingStrategy); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java index ffe2fec380a8..46956d74cc5c 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -41,8 +41,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.BackgroundThread; import com.android.server.display.BrightnessThrottler.Injector; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; -import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; +import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; import com.android.server.display.mode.DisplayModeDirectorTest; import org.junit.Before; @@ -54,6 +54,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @SmallTest @@ -92,7 +93,7 @@ public class BrightnessThrottlerTest { ///////////////// @Test - public void testBrightnessThrottlingData() { + public void testThermalBrightnessThrottlingData() { List<ThrottlingLevel> singleLevel = new ArrayList<>(); singleLevel.add(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f)); @@ -119,34 +120,32 @@ public class BrightnessThrottlerTest { PowerManager.BRIGHTNESS_MAX + EPSILON)); // Test invalid data - BrightnessThrottlingData data; - data = BrightnessThrottlingData.create((List<ThrottlingLevel>)null); + ThermalBrightnessThrottlingData data; + data = ThermalBrightnessThrottlingData.create((List<ThrottlingLevel>) null); assertEquals(data, null); - data = BrightnessThrottlingData.create((BrightnessThrottlingData)null); + data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>()); assertEquals(data, null); - data = BrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>()); + data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedThermalLevels); + data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedBrightnessLevels); + data = ThermalBrightnessThrottlingData.create(unsortedLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedLevels); - assertEquals(data, null); - data = BrightnessThrottlingData.create(invalidLevel); + data = ThermalBrightnessThrottlingData.create(invalidLevel); assertEquals(data, null); // Test valid data - data = BrightnessThrottlingData.create(singleLevel); + data = ThermalBrightnessThrottlingData.create(singleLevel); assertNotEquals(data, null); assertThrottlingLevelsEquals(singleLevel, data.throttlingLevels); - data = BrightnessThrottlingData.create(validLevels); + data = ThermalBrightnessThrottlingData.create(validLevels); assertNotEquals(data, null); assertThrottlingLevelsEquals(validLevels, data.throttlingLevels); } @Test - public void testThrottlingUnsupported() throws Exception { + public void testThermalThrottlingUnsupported() { final BrightnessThrottler throttler = createThrottlerUnsupported(); assertFalse(throttler.deviceSupportsThrottling()); @@ -158,13 +157,13 @@ public class BrightnessThrottlerTest { } @Test - public void testThrottlingSingleLevel() throws Exception { + public void testThermalThrottlingSingleLevel() throws Exception { final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); - final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); final BrightnessThrottler throttler = createThrottlerSupported(data); assertTrue(throttler.deviceSupportsThrottling()); @@ -213,7 +212,7 @@ public class BrightnessThrottlerTest { } @Test - public void testThrottlingMultiLevel() throws Exception { + public void testThermalThrottlingMultiLevel() throws Exception { final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.62f); final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, @@ -222,7 +221,7 @@ public class BrightnessThrottlerTest { List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(levelLo); levels.add(levelHi); - final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); final BrightnessThrottler throttler = createThrottlerSupported(data); assertTrue(throttler.deviceSupportsThrottling()); @@ -293,51 +292,32 @@ public class BrightnessThrottlerTest { assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason()); } - @Test public void testUpdateThrottlingData() throws Exception { + @Test public void testUpdateThermalThrottlingData() throws Exception { // Initialise brightness throttling levels // Ensure that they are overridden by setting the data through device config. final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); - final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); - mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4"); + final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.4"); final BrightnessThrottler throttler = createThrottlerSupported(data); verify(mThermalServiceMock).registerThermalEventListenerWithType( mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f); - // Set status too low to trigger throttling - listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); - mTestLooper.dispatchAll(); - assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); - assertFalse(throttler.isThrottled()); - - // Set status high enough to trigger throttling - listener.notifyThrottling(getSkinTemp(level.thermalStatus)); - mTestLooper.dispatchAll(); - assertEquals(0.4f, throttler.getBrightnessCap(), 0f); - assertTrue(throttler.isThrottled()); - - // Update thresholds - // This data is equivalent to the string "123,1,critical,0.8", passed below - final ThrottlingLevel newLevel = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, - 0.8f); // Set new (valid) data from device config - mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8"); - - // Set status too low to trigger throttling - listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus - 1)); - mTestLooper.dispatchAll(); - assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); - assertFalse(throttler.isThrottled()); - - // Set status high enough to trigger throttling - listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus)); - mTestLooper.dispatchAll(); - assertEquals(newLevel.brightness, throttler.getBrightnessCap(), 0f); - assertTrue(throttler.isThrottled()); + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); + + mDeviceConfigFake.setThermalBrightnessThrottlingData( + "123,1,critical,0.75;123,1,critical,0.99,id_2"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f); + mDeviceConfigFake.setThermalBrightnessThrottlingData( + "123,1,critical,0.8,default;123,1,critical,0.99,id_2"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); } @Test public void testInvalidThrottlingStrings() throws Exception { @@ -347,33 +327,54 @@ public class BrightnessThrottlerTest { 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); - final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); final BrightnessThrottler throttler = createThrottlerSupported(data); verify(mThermalServiceMock).registerThermalEventListenerWithType( mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN)); final IThermalEventListener listener = mThermalEventListenerCaptor.getValue(); // None of these are valid so shouldn't override the original data - mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4"); // Not the current id - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4"); // Incorrect number - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4"); // Incorrect number - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4"); // Invalid level - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3"); // Invalid brightness - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData("invalid string"); // Invalid format - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); - mDeviceConfigFake.setBrightnessThrottlingData(""); // Invalid format - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + + // Not the current id + mDeviceConfigFake.setThermalBrightnessThrottlingData("321,1,critical,0.4"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Incorrect number + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,0,critical,0.4"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Incorrect number + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,2,critical,0.4"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid level + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,invalid,0.4"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid brightness + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,none"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid brightness + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,-3"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid format + mDeviceConfigFake.setThermalBrightnessThrottlingData("invalid string"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid format + mDeviceConfigFake.setThermalBrightnessThrottlingData(""); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid string format + mDeviceConfigFake.setThermalBrightnessThrottlingData( + "123,default,1,critical,0.75,1,critical,0.99"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid level string and number string + mDeviceConfigFake.setThermalBrightnessThrottlingData( + "123,1,1,critical,0.75,id_2,1,critical,0.99"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + // Invalid format - (two default ids for same display) + mDeviceConfigFake.setThermalBrightnessThrottlingData( + "123,1,critical,0.75,default;123,1,critical,0.99"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); } - private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener, - float tooLowCap, float tooHighCap) throws Exception { + private void testThermalThrottling(BrightnessThrottler throttler, + IThermalEventListener listener, float tooLowCap, float tooHighCap) throws Exception { final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, tooHighCap); @@ -396,7 +397,7 @@ public class BrightnessThrottlerTest { 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); - final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels); + final ThermalBrightnessThrottlingData data = ThermalBrightnessThrottlingData.create(levels); // These are identical to the string set below final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, @@ -406,7 +407,7 @@ public class BrightnessThrottlerTest { final ThrottlingLevel levelEmergency = new ThrottlingLevel( PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f); - mDeviceConfigFake.setBrightnessThrottlingData( + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,3,severe,0.9,critical,0.5,emergency,0.1"); final BrightnessThrottler throttler = createThrottlerSupported(data); @@ -472,13 +473,18 @@ public class BrightnessThrottlerTest { } private BrightnessThrottler createThrottlerUnsupported() { - return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, null, () -> {}, null); + return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, + /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null, + /* thermalThrottlingDataId= */ null, + /* thermalThrottlingDataMap= */ new HashMap<>(1)); } - private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) { + private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) { assertNotNull(data); + HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1); + throttlingDataMap.put("default", data); return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(), - data, () -> {}, "123"); + () -> {}, "123", "default", throttlingDataMap); } private Temperature getSkinTemp(@ThrottlingStatus int status) { diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index a7d3df90751f..130e6ad91b49 100644 --- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -84,11 +84,11 @@ public class DeviceStateToLayoutMapTest { } @Test - public void testBrightnessThrottlingMapId() { + public void testThermalBrightnessThrottlingMapId() { Layout configLayout = mDeviceStateToLayoutMap.get(2); - assertEquals("concurrent1", configLayout.getAt(0).getBrightnessThrottlingMapId()); - assertEquals("concurrent2", configLayout.getAt(1).getBrightnessThrottlingMapId()); + assertEquals("concurrent1", configLayout.getAt(0).getThermalBrightnessThrottlingMapId()); + assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId()); } @Test @@ -108,7 +108,7 @@ public class DeviceStateToLayoutMapTest { } @Test - public void testRefreshRateThermalThrottlingMapId() { + public void testThermalRefreshRateThrottlingMapId() { Layout configLayout = mDeviceStateToLayoutMap.get(4); assertEquals("test2", configLayout.getAt(0).getRefreshRateThermalThrottlingMapId()); @@ -124,12 +124,14 @@ public class DeviceStateToLayoutMapTest { /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null, mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ "brightness1", - /* refreshRateZoneId= */ "zone1", /* refreshRateThermalThrottlingMapId= */ "rr1"); + /* refreshRateZoneId= */ "zone1", + /* refreshRateThermalThrottlingMapId= */ "rr1"); testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1", mDisplayIdProducerMock, Layout.Display.POSITION_REAR, Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ "brightness2", - /* refreshRateZoneId= */ "zone2", /* refreshRateThermalThrottlingMapId= */ "rr2"); + /* refreshRateZoneId= */ "zone2", + /* refreshRateThermalThrottlingMapId= */ "rr2"); assertEquals(testLayout, configLayout); } @@ -147,7 +149,8 @@ public class DeviceStateToLayoutMapTest { layout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(id), /* isDefault= */ false, enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null, - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, + /* refreshRateThermalThrottlingMapId= */ null); } private void setupDeviceStateToLayoutMap() throws IOException { diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java index 9fd647bb0b90..5837b21b89fd 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -50,6 +50,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; @SmallTest @@ -186,50 +187,72 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new int[]{-1, 10, 20, 30, 40}, mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); - List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel> throttlingLevels = - new ArrayList(); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel> + defaultThrottlingLevels = new ArrayList<>(); + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + defaultThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f )); - assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels), - mDisplayDeviceConfig.getBrightnessThrottlingData("default")); - throttlingLevels.clear(); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData = + new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels); + + List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel> + concurrentThrottlingLevels = new ArrayList<>(); + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f )); - throttlingLevels.add(new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + concurrentThrottlingLevels.add( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f )); - assertEquals(new DisplayDeviceConfig.BrightnessThrottlingData(throttlingLevels), - mDisplayDeviceConfig.getBrightnessThrottlingData("concurrent")); + DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData = + new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels); + + HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap = + new HashMap<>(2); + throttlingDataMap.put("default", defaultThrottlingData); + throttlingDataMap.put("concurrent", concurrentThrottlingData); + + assertEquals(throttlingDataMap, + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()); assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); @@ -246,8 +269,7 @@ public final class DisplayDeviceConfigTest { mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f), SMALL_DELTA); - - // Todo: Add asserts for BrightnessThrottlingData, DensityMapping, + // Todo: Add asserts for DensityMapping, // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. } @@ -329,16 +351,16 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE); - // Todo: Add asserts for BrightnessThrottlingData, DensityMapping, + // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping, // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor. } @Test - public void testRefreshRateThermalThrottlingFromDisplayConfig() throws IOException { + public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(); SparseArray<SurfaceControl.RefreshRateRange> defaultMap = - mDisplayDeviceConfig.getRefreshRateThrottlingData(null); + mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null); assertNotNull(defaultMap); assertEquals(2, defaultMap.size()); assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA); @@ -347,7 +369,7 @@ public final class DisplayDeviceConfigTest { assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA); SparseArray<SurfaceControl.RefreshRateRange> testMap = - mDisplayDeviceConfig.getRefreshRateThrottlingData("test"); + mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test"); assertNotNull(testMap); assertEquals(1, testMap.size()); assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA); diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java index 567548e83dc8..7536c7949bcb 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -649,9 +649,9 @@ public class LogicalDisplayMapperTest { assertEquals(0, mLogicalDisplayMapper.getDisplayLocked(device2) .getLeadDisplayIdLocked()); assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device1) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); assertEquals("concurrent", mLogicalDisplayMapper.getDisplayLocked(device2) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); mLogicalDisplayMapper.setDeviceStateLocked(1, false); advanceTime(1000); @@ -661,10 +661,10 @@ public class LogicalDisplayMapperTest { assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); assertEquals(DisplayDeviceConfig.DEFAULT_ID, mLogicalDisplayMapper.getDisplayLocked(device1) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); assertEquals(DisplayDeviceConfig.DEFAULT_ID, mLogicalDisplayMapper.getDisplayLocked(device2) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); mLogicalDisplayMapper.setDeviceStateLocked(2, false); advanceTime(1000); @@ -674,10 +674,10 @@ public class LogicalDisplayMapperTest { assertFalse(mLogicalDisplayMapper.getDisplayLocked(device2).isInTransitionLocked()); assertEquals(DisplayDeviceConfig.DEFAULT_ID, mLogicalDisplayMapper.getDisplayLocked(device1) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); assertEquals(DisplayDeviceConfig.DEFAULT_ID, mLogicalDisplayMapper.getDisplayLocked(device2) - .getBrightnessThrottlingDataIdLocked()); + .getThermalBrightnessThrottlingDataIdLocked()); } @Test @@ -863,7 +863,7 @@ public class LogicalDisplayMapperTest { layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer, Layout.Display.POSITION_UNKNOWN, Display.DEFAULT_DISPLAY, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThrottlingMapId= */ null); + /* refreshRateThermalThrottlingMapId= */ null); } private void advanceTime(long timeMs) { diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index db5a46976748..6907145542cc 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -2583,7 +2583,7 @@ public class DisplayModeDirectorTest { KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); } - public void setBrightnessThrottlingData(String brightnessThrottlingData) { + public void setThermalBrightnessThrottlingData(String brightnessThrottlingData) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData); } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 0b6756d7c063..6bcda3fbcf43 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -178,6 +178,8 @@ public final class UserManagerTest { UserHandle.of(userInfo.id)); assertThat(userContext.getSystemService( UserManager.class).isMediaSharedWithParent()).isTrue(); + assertThat(Settings.Secure.getInt(userContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0)).isEqualTo(1); List<UserInfo> list = mUserManager.getUsers(); List<UserInfo> cloneUsers = list.stream().filter( diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java index b2843d82a08a..de82854d42ee 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java @@ -422,7 +422,8 @@ public class DexMetadataHelperTest { null /* splitNames */, null /* isFeatureSplits */, null /* usesSplitNames */, null /* configForSplit */, null /* splitApkPaths */, null /* splitRevisionCodes */, baseApk.getTargetSdkVersion(), - null /* requiredSplitTypes */, null /* splitTypes */); + null /* requiredSplitTypes */, null /* splitTypes */, + false /* allowUpdateOwnership */); Assert.assertEquals(dm.length(), DexMetadataHelper.getPackageDexMetadataSize(pkgLite)); } 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 dc954a2433c5..eceb589bba76 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -28,6 +28,7 @@ import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; +import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; @@ -227,6 +228,7 @@ import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemService.TargetUser; import com.android.server.UiServiceTestCase; +import com.android.server.job.JobSchedulerInternal; import com.android.server.lights.LightsManager; import com.android.server.lights.LogicalLight; import com.android.server.notification.NotificationManagerService.NotificationAssistants; @@ -335,6 +337,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock ActivityManagerInternal mAmi; @Mock + JobSchedulerInternal mJsi; + @Mock private Looper mMainLooper; @Mock private NotificationManager mMockNm; @@ -443,6 +447,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmi); + LocalServices.removeServiceForTest(JobSchedulerInternal.class); + LocalServices.addService(JobSchedulerInternal.class, mJsi); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); LocalServices.removeServiceForTest(PermissionPolicyInternal.class); @@ -1251,7 +1257,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); update = new NotificationChannel("blockedbyuser", "name", IMPORTANCE_NONE); - update.setFgServiceShown(true); + update.setUserVisibleTaskShown(true); mBinderService.updateNotificationChannelForPackage(PKG, mUid, update); waitForIdle(); assertEquals(IMPORTANCE_NONE, mBinderService.getNotificationChannel( @@ -4734,7 +4740,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testBumpFGImportance_noChannelChangePreOApp() throws Exception { + public void testBumpFGImportance_channelChangePreOApp() throws Exception { String preOPkg = PKG_N_MR1; final ApplicationInfo legacy = new ApplicationInfo(); legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; @@ -4752,7 +4758,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setPriority(Notification.PRIORITY_MIN); StatusBarNotification sbn = new StatusBarNotification(preOPkg, preOPkg, 9, - "testBumpFGImportance_noChannelChangePreOApp", + "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(), 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); @@ -4772,11 +4778,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setPriority(Notification.PRIORITY_MIN); sbn = new StatusBarNotification(preOPkg, preOPkg, 9, - "testBumpFGImportance_noChannelChangePreOApp", Binder.getCallingUid(), + "testBumpFGImportance_channelChangePreOApp", Binder.getCallingUid(), 0, nb.build(), UserHandle.getUserHandleForUid(Binder.getCallingUid()), null, 0); mBinderService.enqueueNotificationWithTag(preOPkg, preOPkg, - "testBumpFGImportance_noChannelChangePreOApp", + "testBumpFGImportance_channelChangePreOApp", sbn.getId(), sbn.getNotification(), sbn.getUserId()); waitForIdle(); assertEquals(IMPORTANCE_LOW, @@ -4784,7 +4790,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationChannel defaultChannel = mBinderService.getNotificationChannel( preOPkg, mContext.getUserId(), preOPkg, NotificationChannel.DEFAULT_CHANNEL_ID); - assertEquals(IMPORTANCE_UNSPECIFIED, defaultChannel.getImportance()); + assertEquals(IMPORTANCE_LOW, defaultChannel.getImportance()); } @Test @@ -8609,7 +8615,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertNotNull(n.publicVersion.bigContentView); assertNotNull(n.publicVersion.headsUpContentView); - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); assertNull(n.contentView); assertNull(n.bigContentView); @@ -10439,7 +10445,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFullScreenIntent(mock(PendingIntent.class), true) .build(); - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); final int stickyFlag = n.flags & Notification.FLAG_FSI_REQUESTED_BUT_DENIED; @@ -10524,7 +10530,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .setFlag(FLAG_CAN_COLORIZE, true) .build(); - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); assertFalse(n.isForegroundService()); assertFalse(n.hasColorizedPermission()); @@ -10552,7 +10558,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should not be set assertSame(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10571,7 +10577,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be set assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10595,7 +10601,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be set assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10612,7 +10618,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should not be set assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10630,7 +10636,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.flags |= Notification.FLAG_NO_DISMISS; // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be cleared assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10648,7 +10654,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should not be set assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10669,7 +10675,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { n.flags |= Notification.FLAG_NO_DISMISS; // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be cleared assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10686,7 +10692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should not be set assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10705,7 +10711,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be set assertNotSame(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10733,7 +10739,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should be cleared assertEquals(0, n.flags & Notification.FLAG_NO_DISMISS); @@ -10754,12 +10760,589 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .build(); // When: fix the notification with NotificationManagerService - mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE); + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); // Then: the notification's flag FLAG_NO_DISMISS should not be set assertSame(0, n.flags & Notification.FLAG_NO_DISMISS); } + @Test + public void testCancelAllNotifications_IgnoreUserInitiatedJob() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCancelAllNotifications_IgnoreUserInitiatedJob", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelAllNotifications(PKG, sbn.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(1, notifs.length); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testCancelAllNotifications_UijFlag_NoUij_Allowed() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(false); + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCancelAllNotifications_UijFlag_NoUij_Allowed", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelAllNotifications(PKG, sbn.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelAllNotificationsOtherPackage_IgnoresUijNotification() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCancelAllNotifications_IgnoreOtherPackages", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelAllNotifications("other_pkg_name", sbn.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(1, notifs.length); + assertEquals(1, mService.getNotificationRecordCount()); + } + + @Test + public void testRemoveUserInitiatedJobFlag_ImmediatelyAfterEnqueue() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + StatusBarNotification sbn = new StatusBarNotification("a", "a", 0, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, null, + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mInternalService.removeUserInitiatedJobFlagFromNotification(PKG, sbn.getId(), + sbn.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertFalse(notifs[0].getNotification().isUserInitiatedJob()); + } + + @Test + public void testCancelAfterSecondEnqueueDoesNotSpecifyUserInitiatedJobFlag() throws Exception { + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT | FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + sbn.getNotification().flags = Notification.FLAG_ONGOING_EVENT; + mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(), + sbn.getUserId()); + waitForIdle(); + assertEquals(0, mBinderService.getActiveNotifications(sbn.getPackageName()).length); + assertEquals(0, mService.getNotificationRecordCount()); + } + + @Test + public void testCancelNotificationWithTag_fromApp_cannotCancelUijChild() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationWithTag_fromApp_cannotCancelUijParent() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.getBinderService().cancelNotificationWithTag( + parent.getSbn().getPackageName(), parent.getSbn().getPackageName(), + parent.getSbn().getTag(), parent.getSbn().getId(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(3, notifs.length); + } + + @Test + public void testCancelAllNotificationsFromApp_cannotCancelUijChild() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelAllNotifications_fromApp_cannotCancelUijParent() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + mService.isSystemUid = false; + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelAllNotifications( + parent.getSbn().getPackageName(), parent.getSbn().getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(1, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithUijParent() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_GroupWithUijChild() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_clearAll_Uij() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mService.addNotification(child2); + mService.getBinderService().cancelNotificationsFromListener(null, null); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child2.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithUijParent() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + parent.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_GroupWithUijChild() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + String[] keys = {parent.getSbn().getKey(), child.getSbn().getKey(), + child2.getSbn().getKey(), newGroup.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testCancelNotificationsFromListener_byKey_Uij() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 3, null, false); + child.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mService.addNotification(child); + String[] keys = {child.getSbn().getKey()}; + mService.getBinderService().cancelNotificationsFromListener(null, keys); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(child.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testUserInitiatedCancelAllWithGroup_UserInitiatedFlag() throws Exception { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + final NotificationRecord parent = generateNotificationRecord( + mTestNotificationChannel, 1, "group", true); + final NotificationRecord child = generateNotificationRecord( + mTestNotificationChannel, 2, "group", false); + final NotificationRecord child2 = generateNotificationRecord( + mTestNotificationChannel, 3, "group", false); + child2.getNotification().flags |= FLAG_USER_INITIATED_JOB; + final NotificationRecord newGroup = generateNotificationRecord( + mTestNotificationChannel, 4, "group2", false); + mService.addNotification(parent); + mService.addNotification(child); + mService.addNotification(child2); + mService.addNotification(newGroup); + mService.mNotificationDelegate.onClearAll(mUid, Binder.getCallingPid(), parent.getUserId()); + waitForIdle(); + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(parent.getSbn().getPackageName()); + assertEquals(0, notifs.length); + } + + @Test + public void testDeleteChannelGroupChecksForUijs() throws Exception { + when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid))) + .thenReturn(singletonList(mock(AssociationInfo.class))); + CountDownLatch latch = new CountDownLatch(2); + mService.createNotificationChannelGroup(PKG, mUid, + new NotificationChannelGroup("group", "group"), true, false); + new Thread(() -> { + NotificationChannel notificationChannel = new NotificationChannel("id", "id", + NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("group"); + ParceledListSlice<NotificationChannel> pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + latch.countDown(); + }).start(); + new Thread(() -> { + try { + synchronized (this) { + wait(5000); + } + mService.createNotificationChannelGroup(PKG, mUid, + new NotificationChannelGroup("new", "new group"), true, false); + NotificationChannel notificationChannel = + new NotificationChannel("id", "id", NotificationManager.IMPORTANCE_HIGH); + notificationChannel.setGroup("new"); + ParceledListSlice<NotificationChannel> pls = + new ParceledListSlice(ImmutableList.of(notificationChannel)); + try { + mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls); + mBinderService.deleteNotificationChannelGroup(PKG, "group"); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } catch (Exception e) { + e.printStackTrace(); + } + latch.countDown(); + }).start(); + + latch.await(); + verify(mJsi).isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + anyString(), anyInt(), anyString()); + } + + @Test + public void testRemoveUserInitiatedJobFlagFromNotification_enqueued() { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + Notification n = new Notification.Builder(mContext, "").build(); + n.flags |= FLAG_USER_INITIATED_JOB; + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + + mInternalService.removeUserInitiatedJobFlagFromNotification( + PKG, r.getSbn().getId(), r.getSbn().getUserId()); + + waitForIdle(); + + verify(mListeners, timeout(200).times(0)).notifyPostedLocked(any(), any()); + } + + @Test + public void testRemoveUserInitiatedJobFlagFromNotification_posted() { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + Notification n = new Notification.Builder(mContext, "").build(); + n.flags |= FLAG_USER_INITIATED_JOB; + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mInternalService.removeUserInitiatedJobFlagFromNotification( + PKG, r.getSbn().getId(), r.getSbn().getUserId()); + + waitForIdle(); + + ArgumentCaptor<NotificationRecord> captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).notifyPostedLocked(captor.capture(), any()); + + assertEquals(0, captor.getValue().getNotification().flags); + } + + @Test + public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_enqueued() { + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) { + Notification n = new Notification.Builder(mContext, "").build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + mService.addEnqueuedNotification(r); + } + Notification n = new Notification.Builder(mContext, "").build(); + n.flags |= FLAG_USER_INITIATED_JOB; + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, + NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + + mInternalService.removeUserInitiatedJobFlagFromNotification( + PKG, r.getSbn().getId(), r.getSbn().getUserId()); + + waitForIdle(); + + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, + mService.getNotificationRecordCount()); + } + + @Test + public void testCannotRemoveUserInitiatedJobFlagWhenOverLimit_posted() { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) { + Notification n = new Notification.Builder(mContext, "").build(); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + mService.addNotification(r); + } + Notification n = new Notification.Builder(mContext, "").build(); + n.flags |= FLAG_USER_INITIATED_JOB; + + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, + NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mInternalService.removeUserInitiatedJobFlagFromNotification( + PKG, r.getSbn().getId(), r.getSbn().getUserId()); + + waitForIdle(); + + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, + mService.getNotificationRecordCount()); + } + + @Test + public void testCanPostUijWhenOverLimit() throws RemoteException { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) { + StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel, + i, null, false).getSbn(); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCanPostUijWhenOverLimit", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + } + + final StatusBarNotification sbn = generateNotificationRecord(null).getSbn(); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCanPostUijWhenOverLimit - uij over limit!", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + + waitForIdle(); + + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length); + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, + mService.getNotificationRecordCount()); + } + + @Test + public void testCannotPostNonUijWhenOverLimit() throws RemoteException { + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(true); + for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) { + StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel, + i, null, false).getSbn(); + mBinderService.enqueueNotificationWithTag(PKG, PKG, "testCannotPostNonUijWhenOverLimit", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + } + + final StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel, + 100, null, false).getSbn(); + sbn.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCannotPostNonUijWhenOverLimit - uij over limit!", + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + + final StatusBarNotification sbn2 = generateNotificationRecord(mTestNotificationChannel, + 101, null, false).getSbn(); + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCannotPostNonUijWhenOverLimit - non uij over limit!", + sbn2.getId(), sbn2.getNotification(), sbn2.getUserId()); + + when(mJsi.isNotificationAssociatedWithAnyUserInitiatedJobs(anyInt(), anyInt(), anyString())) + .thenReturn(false); + final StatusBarNotification sbn3 = generateNotificationRecord(mTestNotificationChannel, + 101, null, false).getSbn(); + sbn3.getNotification().flags |= FLAG_USER_INITIATED_JOB; + mBinderService.enqueueNotificationWithTag(PKG, PKG, + "testCannotPostNonUijWhenOverLimit - fake uij over limit!", + sbn3.getId(), sbn3.getNotification(), sbn3.getUserId()); + + waitForIdle(); + + StatusBarNotification[] notifs = + mBinderService.getActiveNotifications(sbn.getPackageName()); + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, notifs.length); + assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS + 1, + mService.getNotificationRecordCount()); + } + + @Test + public void fixNotification_withUijFlag_butIsNotUij() throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + + Notification n = new Notification.Builder(mContext, "test") + .setFlag(FLAG_USER_INITIATED_JOB, true) + .build(); + + mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE, true); + assertFalse(n.isUserInitiatedJob()); + } + private void setDpmAppOppsExemptFromDismissal(boolean isOn) { DeviceConfig.setProperty( DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 893f53825bbb..3ba94000d4a5 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -120,12 +120,20 @@ public class ZenModeConfigTest extends UiServiceTestCase { .showLights(false) .showBadges(false) .showInAmbientDisplay(false) + .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED) + .allowConversations(ZenPolicy.CONVERSATION_SENDERS_NONE) .build(); ZenModeConfig config = getMutedAllConfig(); config.allowAlarms = true; config.allowReminders = true; config.allowEvents = true; + config.allowCalls = true; + config.allowCallsFrom = Policy.PRIORITY_SENDERS_CONTACTS; + config.allowMessages = true; + config.allowMessagesFrom = Policy.PRIORITY_SENDERS_STARRED; + config.allowConversations = false; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_BADGE; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_LIGHTS; config.suppressedVisualEffects |= Policy.SUPPRESSED_EFFECT_AMBIENT; @@ -138,6 +146,10 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(expected.getPriorityCategoryEvents(), actual.getPriorityCategoryEvents()); assertEquals(expected.getVisualEffectLights(), actual.getVisualEffectLights()); assertEquals(expected.getVisualEffectAmbient(), actual.getVisualEffectAmbient()); + assertEquals(expected.getPriorityConversationSenders(), + actual.getPriorityConversationSenders()); + assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders()); + assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index e85b574baa22..5282585e9757 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -147,8 +147,8 @@ public class DimmerTests extends WindowTestsBase { int width = 100; int height = 300; - Rect bounds = new Rect(0, 0, width, height); - mDimmer.updateDims(mTransaction, bounds); + mDimmer.mDimState.mDimBounds.set(0, 0, width, height); + mDimmer.updateDims(mTransaction); verify(mTransaction).setWindowCrop(getDimLayer(), width, height); verify(mTransaction).show(getDimLayer()); @@ -194,7 +194,7 @@ public class DimmerTests extends WindowTestsBase { SurfaceControl dimLayer = getDimLayer(); mDimmer.resetDimStates(); - mDimmer.updateDims(mTransaction, new Rect()); + mDimmer.updateDims(mTransaction); verify(mSurfaceAnimatorStarter).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); @@ -212,29 +212,29 @@ public class DimmerTests extends WindowTestsBase { mDimmer.resetDimStates(); mDimmer.dimAbove(mTransaction, child, alpha); - mDimmer.updateDims(mTransaction, new Rect()); + mDimmer.updateDims(mTransaction); verify(mTransaction).show(dimLayer); verify(mTransaction, never()).remove(dimLayer); } @Test public void testDimUpdateWhileDimming() { - Rect bounds = new Rect(); TestWindowContainer child = new TestWindowContainer(mWm); mHost.addChild(child, 0); final float alpha = 0.8f; mDimmer.dimAbove(mTransaction, child, alpha); + final Rect bounds = mDimmer.mDimState.mDimBounds; SurfaceControl dimLayer = getDimLayer(); bounds.set(0, 0, 10, 10); - mDimmer.updateDims(mTransaction, bounds); + mDimmer.updateDims(mTransaction); verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height()); verify(mTransaction, times(1)).show(dimLayer); verify(mTransaction).setPosition(dimLayer, 0, 0); bounds.set(10, 10, 30, 30); - mDimmer.updateDims(mTransaction, bounds); + mDimmer.updateDims(mTransaction); verify(mTransaction).setWindowCrop(dimLayer, bounds.width(), bounds.height()); verify(mTransaction).setPosition(dimLayer, 10, 10); } @@ -246,13 +246,13 @@ public class DimmerTests extends WindowTestsBase { mDimmer.dimAbove(mTransaction, child, 1); SurfaceControl dimLayer = getDimLayer(); - mDimmer.updateDims(mTransaction, new Rect()); + mDimmer.updateDims(mTransaction); verify(mTransaction, times(1)).show(dimLayer); reset(mSurfaceAnimatorStarter); mDimmer.dontAnimateExit(); mDimmer.resetDimStates(); - mDimmer.updateDims(mTransaction, new Rect()); + mDimmer.updateDims(mTransaction); verify(mSurfaceAnimatorStarter, never()).startAnimation(any(SurfaceAnimator.class), any( SurfaceControl.Transaction.class), any(AnimationAdapter.class), anyBoolean(), eq(ANIMATION_TYPE_DIMMER)); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 43fc1c43d6ba..7cb7c79d63a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -111,7 +111,6 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500) .setUniqueId(mDisplayUniqueId).build(); - mTestDisplay.getDefaultTaskDisplayArea().setWindowingMode(TEST_WINDOWING_MODE); when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) .thenReturn(mTestDisplay); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 12e4825e5f85..a15ee694d6a4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -41,6 +41,7 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_V import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; +import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -146,7 +147,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mActivity = setUpActivityWithComponent(); mController = new LetterboxUiController(mWm, mActivity); prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch(); - mController.setRelauchingAfterRequestedOrientationChanged(false); + mController.setRelaunchingAfterRequestedOrientationChanged(false); spyOn(mDisplayContent.mDisplayRotationCompatPolicy); doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy) @@ -190,7 +191,28 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test public void testShouldIgnoreOrientationRequestLoop_overrideDisabled_returnsFalse() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // Request 3 times to simulate orientation request loop + for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { + assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, + /* expectedCount */ 0); + } + } + + @Test + @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) + public void testShouldIgnoreOrientationRequestLoop_propertyIsFalseAndOverride_returnsFalse() + throws Exception { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); + mockThatProperty(PROPERTY_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED, + /* value */ false); doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + + mController = new LetterboxUiController(mWm, mActivity); + // Request 3 times to simulate orientation request loop for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, @@ -201,7 +223,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) public void testShouldIgnoreOrientationRequestLoop_isLetterboxed_returnsFalse() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // Request 3 times to simulate orientation request loop for (int i = 0; i <= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, @@ -212,7 +237,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) public void testShouldIgnoreOrientationRequestLoop_noLoop_returnsFalse() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + // No orientation request loop assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, /* expectedCount */ 0); @@ -222,7 +250,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) public void testShouldIgnoreOrientationRequestLoop_timeout_returnsFalse() throws InterruptedException { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + for (int i = MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i > 0; i--) { assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, /* expectedCount */ 0); @@ -233,7 +264,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED}) public void testShouldIgnoreOrientationRequestLoop_returnsTrue() { + doReturn(true).when(mLetterboxConfiguration) + .isPolicyForIgnoringRequestedOrientationEnabled(); doReturn(false).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio(); + for (int i = 0; i < MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP; i++) { assertShouldIgnoreOrientationRequestLoop(/* shouldIgnore */ false, /* expectedCount */ i); @@ -870,7 +904,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { doReturn(true).when(mLetterboxConfiguration) .isPolicyForIgnoringRequestedOrientationEnabled(); - mController.setRelauchingAfterRequestedOrientationChanged(true); + mController.setRelaunchingAfterRequestedOrientationChanged(true); } private ActivityRecord setUpActivityWithComponent() { diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 9ebc7307418d..10f4158205e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -924,6 +924,11 @@ public class RecentTasksTest extends WindowTestsBase { @Test public void testFreezeTaskListOrder_timeout() { + for (Task t : mTasks) { + // Make all the tasks non-empty + new ActivityBuilder(mAtm).setTask(t).build(); + } + // Add some tasks mRecentTasks.add(mTasks.get(0)); mRecentTasks.add(mTasks.get(1)); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index a646d01378aa..e96d1abf9ced 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -813,6 +813,8 @@ public class SizeCompatTests extends WindowTestsBase { spyOn(mActivity.mLetterboxUiController); doReturn(true).when(mActivity.mLetterboxUiController) + .isSurfaceReadyToShow(any()); + doReturn(true).when(mActivity.mLetterboxUiController) .isSurfaceVisible(any()); assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi( diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index b48fd7d60f06..fdb3502f2ce7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -691,6 +691,7 @@ public class WindowStateTests extends WindowTestsBase { // Child window without scale (e.g. different app) should apply inverse scale of parent. doReturn(1f).when(cmp).getCompatScale(anyString(), anyInt()); final WindowState child2 = createWindow(w, TYPE_APPLICATION_SUB_PANEL, "child2"); + makeWindowVisible(w, child2); clearInvocations(t); child2.prepareSurfaces(); verify(t).setMatrix(child2.mSurfaceControl, w.mInvGlobalScale, 0, 0, w.mInvGlobalScale); diff --git a/tests/OdmApps/Android.bp b/tests/OdmApps/Android.bp index 5f03aa27e6df..a5c6d6513f50 100644 --- a/tests/OdmApps/Android.bp +++ b/tests/OdmApps/Android.bp @@ -28,5 +28,6 @@ java_test_host { test_suites: ["device-tests"], data: [ ":TestOdmApp", + ":TestOdmPrivApp", ], } |