diff options
452 files changed, 10846 insertions, 5448 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/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index c4e8b0e49af3..a8b6c0b70804 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -105,6 +105,7 @@ static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress"; static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; +static const int MAX_CHECK_EXIT_INTERVAL_US = 50000; static constexpr size_t TEXT_POS_LEN_MAX = 16; static const int DYNAMIC_COLOR_COUNT = 4; static const char U_TEXTURE[] = "uTexture"; @@ -1678,7 +1679,17 @@ bool BootAnimation::playAnimation(const Animation& animation) { checkExit(); } - usleep(part.pause * ns2us(frameDuration)); + int pauseDuration = part.pause * ns2us(frameDuration); + while(pauseDuration > 0 && !exitPending()){ + if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) { + usleep(MAX_CHECK_EXIT_INTERVAL_US); + pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US; + } else { + usleep(pauseDuration); + break; + } + checkExit(); + } if (exitPending() && !part.count && mCurrentInset >= mTargetInset && !part.hasFadingPhase()) { diff --git a/core/api/current.txt b/core/api/current.txt index 80abd84733d7..e10fbf20b772 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -13662,7 +13662,6 @@ package android.credentials { } public final class CredentialOption implements android.os.Parcelable { - ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean); method public int describeContents(); method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders(); method @NonNull public android.os.Bundle getCandidateQueryData(); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 873234a04460..2dfda517b495 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 { @@ -2275,12 +2277,12 @@ package android.os { method public int getMainDisplayIdAssignedToUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType(); - method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); + method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String); method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported(); method public boolean isVisibleBackgroundUsersSupported(); - method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; + method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; } public final class VibrationAttributes 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/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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 746b8f70b6af..0b4862176040 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -24,6 +24,7 @@ import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationHistory; import android.app.NotificationManager; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ParceledListSlice; @@ -225,6 +226,7 @@ interface INotificationManager void setNotificationDelegate(String callingPkg, String delegate); String getNotificationDelegate(String callingPkg); boolean canNotifyAsPackage(String callingPkg, String targetPkg, int userId); + boolean canUseFullScreenIntent(in AttributionSource attributionSource); void setPrivateNotificationsAllowed(boolean allow); boolean getPrivateNotificationsAllowed(); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 96d785f0df9c..63da0a231286 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{}; @@ -5727,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); @@ -6868,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. @@ -6986,6 +7016,22 @@ public class Notification implements Parcelable } /** + * @return true for custom notifications, including notifications + * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle, + * and other notifications with user-provided custom views. + * + * @hide + */ + public Boolean isCustomNotification() { + if (contentView == null + && bigContentView == null + && headsUpContentView == null) { + return false; + } + return true; + } + + /** * @return true if this notification is showing as a bubble * * @hide 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..785470f2f22e 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -31,7 +31,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.PermissionChecker; import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Icon; @@ -726,8 +725,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 +740,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 +755,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> * @@ -874,19 +876,11 @@ public class NotificationManager { * {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}. */ public boolean canUseFullScreenIntent() { - final int result = PermissionChecker.checkPermissionForPreflight(mContext, - android.Manifest.permission.USE_FULL_SCREEN_INTENT, - mContext.getAttributionSource()); - - switch (result) { - case PermissionChecker.PERMISSION_GRANTED: - return true; - case PermissionChecker.PERMISSION_SOFT_DENIED: - case PermissionChecker.PERMISSION_HARD_DENIED: - return false; - default: - if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result); - return false; + INotificationManager service = getService(); + try { + return service.canUseFullScreenIntent(mContext.getAttributionSource()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 9bf56b374576..99a7fa21b911 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -841,7 +841,8 @@ public final class PendingIntent implements Parcelable { /** * Perform the operation associated with this PendingIntent. * - * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -855,7 +856,8 @@ public final class PendingIntent implements Parcelable { * * @param code Result code to supply back to the PendingIntent's target. * - * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -875,7 +877,8 @@ public final class PendingIntent implements Parcelable { * original Intent. If flag {@link #FLAG_IMMUTABLE} was set when this * pending intent was created, this argument will be ignored. * - * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -892,6 +895,11 @@ public final class PendingIntent implements Parcelable { * @param options Additional options the caller would like to provide to modify the * sending behavior. May be built from an {@link ActivityOptions} to apply to an * activity start. + * + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. */ public void send(@Nullable Bundle options) throws CanceledException { send(null, 0, null, null, null, null, options); @@ -908,7 +916,8 @@ public final class PendingIntent implements Parcelable { * should happen. If null, the callback will happen from the thread * pool of the process. * - * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -942,11 +951,8 @@ public final class PendingIntent implements Parcelable { * should happen. If null, the callback will happen from the thread * pool of the process. * - * @see #send() - * @see #send(int) - * @see #send(Context, int, Intent) - * @see #send(int, android.app.PendingIntent.OnFinished, Handler) - * @see #send(Context, int, Intent, OnFinished, Handler, String) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -985,11 +991,8 @@ public final class PendingIntent implements Parcelable { * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}. * If null, no permission is required. * - * @see #send() - * @see #send(int) - * @see #send(Context, int, Intent) - * @see #send(int, android.app.PendingIntent.OnFinished, Handler) - * @see #send(Context, int, Intent, OnFinished, Handler) + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String, + * Bundle) * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. @@ -1032,12 +1035,6 @@ public final class PendingIntent implements Parcelable { * @param options Additional options the caller would like to provide to modify the sending * behavior. May be built from an {@link ActivityOptions} to apply to an activity start. * - * @see #send() - * @see #send(int) - * @see #send(Context, int, Intent) - * @see #send(int, android.app.PendingIntent.OnFinished, Handler) - * @see #send(Context, int, Intent, OnFinished, Handler) - * * @throws CanceledException Throws CanceledException if the PendingIntent * is no longer allowing more intents to be sent through it. */ 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 7dabe60e3ba8..4d3338beded5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -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) { diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 593f73635617..052f670b71bd 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -1802,14 +1802,6 @@ public final class DevicePolicyResources { PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE"; /** - * Text for dialog shown when user tries to open a work app when the work profile is - * turned off, confirming that the user wants to turn on access to their - * work apps. - */ - public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE = - PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE"; - - /** * Notification title shown when work profile is credential encrypted and requires * the user to unlock before it's usable. */ diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java index 30aad965c008..7526a7b2c934 100644 --- a/core/java/android/app/admin/IntentFilterPolicyKey.java +++ b/core/java/android/app/admin/IntentFilterPolicyKey.java @@ -28,7 +28,9 @@ import android.content.IntentFilter; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.Log; +import com.android.internal.util.XmlUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; @@ -45,6 +47,10 @@ import java.util.Objects; */ @SystemApi public final class IntentFilterPolicyKey extends PolicyKey { + + private static final String TAG = "IntentFilterPolicyKey"; + + private static final String TAG_INTENT_FILTER_ENTRY = "filter"; private final IntentFilter mFilter; /** @@ -83,7 +89,9 @@ public final class IntentFilterPolicyKey extends PolicyKey { @Override public void saveToXml(TypedXmlSerializer serializer) throws IOException { serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier()); + serializer.startTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY); mFilter.writeToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY); } /** @@ -93,11 +101,27 @@ public final class IntentFilterPolicyKey extends PolicyKey { public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER); - IntentFilter filter = new IntentFilter(); - filter.readFromXml(parser); + IntentFilter filter = readIntentFilterFromXml(parser); return new IntentFilterPolicyKey(identifier, filter); } + @Nullable + private IntentFilter readIntentFilterFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + String tag = parser.getName(); + if (tag.equals(TAG_INTENT_FILTER_ENTRY)) { + IntentFilter filter = new IntentFilter(); + filter.readFromXml(parser); + return filter; + } + Log.e(TAG, "Unknown tag: " + tag); + } + Log.e(TAG, "Error parsing IntentFilterPolicyKey, IntentFilter not found"); + return null; + } + /** * @hide */ diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java index a3874f7cb007..8132b813cba0 100644 --- a/core/java/android/app/search/SearchTarget.java +++ b/core/java/android/app/search/SearchTarget.java @@ -186,16 +186,6 @@ public final class SearchTarget implements Parcelable { mAppWidgetProviderInfo = appWidgetProviderInfo; mSliceUri = sliceUri; mExtras = extras != null ? extras : new Bundle(); - - int published = 0; - if (mSearchAction != null) published++; - if (mShortcutInfo != null) published++; - if (mAppWidgetProviderInfo != null) published++; - if (mSliceUri != null) published++; - if (published > 1) { - throw new IllegalStateException("Only one of SearchAction, ShortcutInfo," - + " AppWidgetProviderInfo, SliceUri can be assigned in a SearchTarget."); - } } /** diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 81fc0293b685..23ba33698739 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -261,14 +261,17 @@ public class UserInfo implements Parcelable { public boolean guestToRemove; /** - * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a + * This is used to optimize the creation of a user, i.e. OEMs might choose to pre-create a * number of users at the first boot, so the actual creation later is faster. * * <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular * user operations (other than user creation per se). * - * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to - * {@code false}. + * <p>Once the pre-created is used to create a "real" user later on, {@code preCreated} is set + * to {@code false}. + * + * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize + * pre-created users in older versions, but will eventually be removed. */ public boolean preCreated; @@ -277,6 +280,9 @@ public class UserInfo implements Parcelable { * user. * * <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel. + * + * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize + * pre-created users in older versions, but will eventually ve removed. */ public boolean convertedFromPreCreated; diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java index d2a6f03cc1a1..ac65933db136 100644 --- a/core/java/android/content/res/AssetFileDescriptor.java +++ b/core/java/android/content/res/AssetFileDescriptor.java @@ -21,20 +21,12 @@ import android.os.Bundle; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; -import android.system.ErrnoException; -import android.system.Os; import java.io.Closeable; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; /** * File descriptor of an entry in the AssetManager. This provides your own @@ -211,26 +203,19 @@ public class AssetFileDescriptor implements Parcelable, Closeable { */ public static class AutoCloseInputStream extends ParcelFileDescriptor.AutoCloseInputStream { - /** Size of current file. */ - private long mTotalSize; - /** The absolute position of current file start point. */ - private final long mFileOffset; - /** The relative position where input stream is against mFileOffset. */ - private long mOffset; - private OffsetCorrectFileChannel mOffsetCorrectFileChannel; + private long mRemaining; public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException { super(fd.getParcelFileDescriptor()); - mTotalSize = fd.getLength(); - mFileOffset = fd.getStartOffset(); + super.skip(fd.getStartOffset()); + mRemaining = (int) fd.getLength(); } @Override public int available() throws IOException { - long available = mTotalSize - mOffset; - return available >= 0 - ? (available < 0x7fffffff ? (int) available : 0x7fffffff) - : 0; + return mRemaining >= 0 + ? (mRemaining < 0x7fffffff ? (int) mRemaining : 0x7fffffff) + : super.available(); } @Override @@ -242,24 +227,15 @@ public class AssetFileDescriptor implements Parcelable, Closeable { @Override public int read(byte[] buffer, int offset, int count) throws IOException { - int available = available(); - if (available <= 0) { - return -1; - } - - if (count > available) count = available; - try { - int res = Os.pread(getFD(), buffer, offset, count, mFileOffset + mOffset); - // pread returns 0 at end of file, while java's InputStream interface requires -1 - if (res == 0) res = -1; - if (res > 0) { - mOffset += res; - updateChannelPosition(mOffset + mFileOffset); - } + if (mRemaining >= 0) { + if (mRemaining == 0) return -1; + if (count > mRemaining) count = (int) mRemaining; + int res = super.read(buffer, offset, count); + if (res >= 0) mRemaining -= res; return res; - } catch (ErrnoException e) { - throw new IOException(e); } + + return super.read(buffer, offset, count); } @Override @@ -269,185 +245,41 @@ public class AssetFileDescriptor implements Parcelable, Closeable { @Override public long skip(long count) throws IOException { - int available = available(); - if (available <= 0) { - return -1; + if (mRemaining >= 0) { + if (mRemaining == 0) return -1; + if (count > mRemaining) count = mRemaining; + long res = super.skip(count); + if (res >= 0) mRemaining -= res; + return res; } - if (count > available) count = available; - mOffset += count; - updateChannelPosition(mOffset + mFileOffset); - return count; + return super.skip(count); } @Override public void mark(int readlimit) { - // Not supported. - return; + if (mRemaining >= 0) { + // Not supported. + return; + } + super.mark(readlimit); } @Override public boolean markSupported() { - return false; + if (mRemaining >= 0) { + return false; + } + return super.markSupported(); } @Override public synchronized void reset() throws IOException { - // Not supported. - return; - } - - @Override - public FileChannel getChannel() { - if (mOffsetCorrectFileChannel == null) { - mOffsetCorrectFileChannel = new OffsetCorrectFileChannel(super.getChannel()); - } - try { - updateChannelPosition(mOffset + mFileOffset); - } catch (IOException e) { - throw new RuntimeException(e); - } - return mOffsetCorrectFileChannel; - } - - /** - * Update the position of mOffsetCorrectFileChannel only after it is constructed. - * - * @param newPosition The absolute position mOffsetCorrectFileChannel needs to be moved to. - */ - private void updateChannelPosition(long newPosition) throws IOException { - if (mOffsetCorrectFileChannel != null) { - mOffsetCorrectFileChannel.position(newPosition); - } - } - - /** - * A FileChannel wrapper that will update mOffset of the AutoCloseInputStream - * to correct position when using FileChannel to read. All occurrence of position - * should be using absolute solution and each override method just do Delegation - * besides additional check. All methods related to write mode have been disabled - * and will throw UnsupportedOperationException with customized message. - */ - private class OffsetCorrectFileChannel extends FileChannel { - private final FileChannel mDelegate; - private static final String METHOD_NOT_SUPPORTED_MESSAGE = - "This Method is not supported in AutoCloseInputStream FileChannel."; - - OffsetCorrectFileChannel(FileChannel fc) { - mDelegate = fc; - } - - @Override - public int read(ByteBuffer dst) throws IOException { - if (available() <= 0) return -1; - int bytesRead = mDelegate.read(dst); - if (bytesRead != -1) mOffset += bytesRead; - return bytesRead; - } - - @Override - public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { - if (available() <= 0) return -1; - if (mOffset + length > mTotalSize) { - length = (int) (mTotalSize - mOffset); - } - long bytesRead = mDelegate.read(dsts, offset, length); - if (bytesRead != -1) mOffset += bytesRead; - return bytesRead; - } - - @Override - /**The only read method that does not move channel position*/ - public int read(ByteBuffer dst, long position) throws IOException { - if (position - mFileOffset > mTotalSize) return -1; - return mDelegate.read(dst, position); - } - - @Override - public long position() throws IOException { - return mDelegate.position(); - } - - @Override - public FileChannel position(long newPosition) throws IOException { - mOffset = newPosition - mFileOffset; - return mDelegate.position(newPosition); - } - - @Override - public long size() throws IOException { - return mTotalSize; - } - - @Override - public long transferTo(long position, long count, WritableByteChannel target) - throws IOException { - if (position - mFileOffset > mTotalSize) { - return 0; - } - if (position - mFileOffset + count > mTotalSize) { - count = mTotalSize - (position - mFileOffset); - } - return mDelegate.transferTo(position, count, target); - } - - @Override - public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { - if (position - mFileOffset > mTotalSize) { - throw new IOException( - "Cannot map to buffer because position exceed current file size."); - } - if (position - mFileOffset + size > mTotalSize) { - size = mTotalSize - (position - mFileOffset); - } - return mDelegate.map(mode, position, size); - } - - @Override - protected void implCloseChannel() throws IOException { - mDelegate.close(); - } - - @Override - public int write(ByteBuffer src) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public long write(ByteBuffer[] srcs, int offset, int length) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public int write(ByteBuffer src, long position) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public long transferFrom(ReadableByteChannel src, long position, long count) - throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public FileChannel truncate(long size) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public void force(boolean metaData) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public FileLock lock(long position, long size, boolean shared) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); - } - - @Override - public FileLock tryLock(long position, long size, boolean shared) throws IOException { - throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE); + if (mRemaining >= 0) { + // Not supported. + return; } + super.reset(); } } diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java index e933123d08b8..df948f17d6e0 100644 --- a/core/java/android/credentials/CredentialOption.java +++ b/core/java/android/credentials/CredentialOption.java @@ -37,8 +37,7 @@ import java.util.Set; /** * Information about a specific type of credential to be requested during a {@link - * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, - * OutcomeReceiver)} operation. + * CredentialManager#getCredential} operation. */ public final class CredentialOption implements Parcelable { @@ -196,9 +195,8 @@ public final class CredentialOption implements Parcelable { * @throws NullPointerException If {@code credentialRetrievalData}, or * {@code candidateQueryData} is null. * - * @deprecated replaced by Builder + * @hide */ - @Deprecated public CredentialOption( @NonNull String type, @NonNull Bundle credentialRetrievalData, diff --git a/core/java/android/hardware/biometrics/ComponentInfoInternal.java b/core/java/android/hardware/biometrics/ComponentInfoInternal.java index 3b61a56bd9f1..2e708de21762 100644 --- a/core/java/android/hardware/biometrics/ComponentInfoInternal.java +++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.java @@ -19,6 +19,8 @@ package android.hardware.biometrics; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; +import android.util.IndentingPrintWriter; /** * The internal class for storing the component info for a subsystem of the biometric sensor, @@ -90,12 +92,19 @@ public class ComponentInfoInternal implements Parcelable { dest.writeString(softwareVersion); } - @Override - public String toString() { - return "ComponentId: " + componentId - + ", HardwareVersion: " + hardwareVersion - + ", FirmwareVersion: " + firmwareVersion - + ", SerialNumber " + serialNumber - + ", SoftwareVersion: " + softwareVersion; + /** + * Print the component info into the given stream. + * + * @param pw The stream to dump the info into. + * @hide + */ + public void dump(@NonNull IndentingPrintWriter pw) { + pw.println(TextUtils.formatSimple("componentId: %s", componentId)); + pw.increaseIndent(); + pw.println(TextUtils.formatSimple("hardwareVersion: %s", hardwareVersion)); + pw.println(TextUtils.formatSimple("firmwareVersion: %s", firmwareVersion)); + pw.println(TextUtils.formatSimple("serialNumber: %s", serialNumber)); + pw.println(TextUtils.formatSimple("softwareVersion: %s", softwareVersion)); + pw.decreaseIndent(); } } diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index c88af5aad738..1a38c8897b76 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -58,10 +58,10 @@ interface IBiometricService { boolean hasEnrolledBiometrics(int userId, String opPackageName); // Registers an authenticator (e.g. face, fingerprint, iris). - // Id must be unique, whereas strength and modality don't need to be. + // Sensor Id in sensor props must be unique, whereas modality doesn't need to be. // TODO(b/123321528): Turn strength and modality into enums. @EnforcePermission("USE_BIOMETRIC_INTERNAL") - void registerAuthenticator(int id, int modality, int strength, + void registerAuthenticator(int modality, in SensorPropertiesInternal props, IBiometricAuthenticator authenticator); // Register callback for when keyguard biometric eligibility changes. diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index b3604da49f5e..24e28e95cd98 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -3706,17 +3706,24 @@ public class UserManager { * {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in * com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION. * + * * @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}. * @return the {@link UserInfo} object for the created user. * * @throws UserOperationException if the user could not be created. + * + * @deprecated Pre-created users are deprecated. This method should no longer be used, and will + * be removed once all the callers are removed. + * * @hide */ + @Deprecated @TestApi @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, Manifest.permission.CREATE_USERS}) public @NonNull UserInfo preCreateUser(@NonNull String userType) throws UserOperationException { + Log.w(TAG, "preCreateUser(): Pre-created user is deprecated."); try { return mService.preCreateUserWithThrow(userType); } catch (ServiceSpecificException e) { @@ -4296,8 +4303,12 @@ public class UserManager { /** * Returns information for all users on this device, based on the filtering parameters. * + * @deprecated Pre-created users are deprecated and no longer supported. + * Use {@link #getUsers()}, {@link #getUsers(boolean)}, or {@link #getAliveUsers()} + * instead. * @hide */ + @Deprecated @TestApi @RequiresPermission(anyOf = { android.Manifest.permission.MANAGE_USERS, diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d34b45bf1ff1..4603e43fd164 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,18 +1,19 @@ # Bug component: 137825 -evanseverson@google.com -evanxinchen@google.com ashfall@google.com -guojing@google.com +augale@google.com +evanseverson@google.com +fayey@google.com jaysullivan@google.com +joecastro@google.com kvakil@google.com mrulhania@google.com narayan@google.com ntmyren@google.com olekarg@google.com pyuli@google.com -raphk@google.com rmacgregor@google.com sergeynv@google.com theianchen@google.com +yutingfang@google.com zhanghai@google.com diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c473d3f81823..7cb959dee245 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3431,7 +3431,7 @@ public final class Settings { + " type:" + mUri.getPath() + " in package:" + cr.getPackageName()); } - for (int i = 0; i < mValues.size(); ++i) { + for (int i = mValues.size() - 1; i >= 0; i--) { String key = mValues.keyAt(i); if (key.startsWith(prefix)) { mValues.remove(key); @@ -18125,12 +18125,6 @@ public final class Settings { public static final String WEAR_OS_VERSION_STRING = "wear_os_version_string"; /** - * Whether the physical button has been set. - * @hide - */ - public static final String BUTTON_SET = "button_set"; - - /** * Whether there is a side button. * @hide */ @@ -18302,6 +18296,12 @@ public final class Settings { public static final int COMPANION_OS_VERSION_UNDEFINED = -1; /** + * The companion App name. + * @hide + */ + public static final String COMPANION_APP_NAME = "wear_companion_app_name"; + + /** * A boolean value to indicate if we want to support all languages in LE edition on * wear. 1 for supporting, 0 for not supporting. * @hide @@ -18413,10 +18413,13 @@ public final class Settings { public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection"; /** - * Whether the device has combined location setting enabled. + * + * @deprecated Use LocationManager as the source of truth for all location states. + * * @hide */ + @Deprecated public static final String COMBINED_LOCATION_ENABLED = "combined_location_enable"; /** @@ -18482,67 +18485,36 @@ public final class Settings { public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED = "clockwork_long_press_to_assistant_enabled"; - /* + /** * Whether the device has Cooldown Mode enabled. * @hide */ public static final String COOLDOWN_MODE_ON = "cooldown_mode_on"; - /* + /** * Whether the device has Wet Mode/ Touch Lock Mode enabled. * @hide */ public static final String WET_MODE_ON = "wet_mode_on"; - /* + /** * Whether the RSB wake feature is enabled. * @hide */ public static final String RSB_WAKE_ENABLED = "rsb_wake_enabled"; - /* + /** * Whether the screen-unlock (keyguard) sound is enabled. * @hide */ public static final String SCREEN_UNLOCK_SOUND_ENABLED = "screen_unlock_sound_enabled"; - /* + /** * Whether charging sounds are enabled. * @hide */ public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled"; - /** The status of the early updates process. - * @hide - */ - public static final String EARLY_UPDATES_STATUS = "early_updates_status"; - - /** - * Early updates not started - * @hide - */ - public static final int EARLY_UPDATES_STATUS_NOT_STARTED = 0; - /** - * Early updates started and in progress - * @hide - */ - public static final int EARLY_UPDATES_STATUS_STARTED = 1; - /** - * Early updates completed and was successful - * @hide - */ - public static final int EARLY_UPDATES_STATUS_SUCCESS = 2; - /** - * Early updates skipped - * @hide - */ - public static final int EARLY_UPDATES_STATUS_SKIPPED = 3; - /** - * Early updates aborted due to timeout - * @hide - */ - public static final int EARLY_UPDATES_STATUS_ABORTED = 4; - /** * Whether dynamic color theming (e.g. Material You) is enabled for apps which support * it. @@ -18669,6 +18641,174 @@ public final class Settings { * @hide */ public static final int UPGRADE_DATA_MIGRATION_DONE = 2; + + /** + * Whether to disable AOD while plugged. + * (0 = false, 1 = true) + * @hide + */ + public static final String DISABLE_AOD_WHILE_PLUGGED = "disable_aod_while_plugged"; + + /** + * Whether the user has consented for network location provider (NLP). + * This setting key will only be used once during OOBE to set NLP initial value through + * the companion app ToS. This setting key will be synced over from Companion and + * corresponding toggle in GMS will be enabled. + * @hide + */ + public static final String NETWORK_LOCATION_OPT_IN = "network_location_opt_in"; + + /** + * The custom foreground color. + * @hide + */ + public static final String CUSTOM_COLOR_FOREGROUND = "custom_foreground_color"; + + /** + * The custom background color. + * @hide + */ + public static final String CUSTOM_COLOR_BACKGROUND = "custom_background_color"; + + /** The status of the phone switching process. + * @hide + */ + public static final String PHONE_SWITCHING_STATUS = "phone_switching_status"; + + /** + * Phone switching not started + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_NOT_STARTED = 0; + + /** + * Phone switching started + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_STARTED = 1; + + /** + * Phone switching completed and was successful + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_SUCCESS = 2; + + /** + * Phone switching was cancelled + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_CANCELLED = 3; + + /** + * Phone switching failed + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_FAILED = 4; + + /** + * Phone switching is in progress of advertising to new companion device. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_ADVERTISING = 5; + + /** + * Phone switching successfully bonded with new companion device. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_BONDED = 6; + + /** + * Phone switching successfully completed on phone side. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_PHONE_COMPLETE = 7; + + /** + * Connection config migration in progress. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION = 8; + + /** + * Connection config migration failed. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_FAILED = 9; + + /** + * Connection config migration cancellation in progress. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_CANCELLED = 10; + + /** + * Connection config migration success. + * @hide + */ + public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11; + + + /** + * Whether the device has enabled the feature to reduce motion and animation + * (0 = false, 1 = true) + * @hide + */ + public static final String REDUCE_MOTION = "reduce_motion"; + + /** + * Whether RTL swipe-to-dismiss is enabled by developer options. + * (0 = false, 1 = true) + * @hide + */ + public static final String RTL_SWIPE_TO_DISMISS_ENABLED_DEV = + "rtl_swipe_to_dismiss_enabled_dev"; + + /** + * Tethered Configuration state. + * @hide + */ + public static final String TETHER_CONFIG_STATE = "tethered_config_state"; + + /** + * Tethered configuration state is unknown. + * @hide + */ + public static final int TETHERED_CONFIG_UNKNOWN = 0; + + /** + * Device is set into standalone mode. + * @hide + */ + public static final int TETHERED_CONFIG_STANDALONE = 1; + + /** + * Device is set in tethered mode. + * @hide + */ + public static final int TETHERED_CONFIG_TETHERED = 2; + + + /** + * Whether phone switching is supported. + * + * (0 = false, 1 = true) + * @hide + */ + public static final String PHONE_SWITCHING_SUPPORTED = "phone_switching_supported"; + + /** + * Setting indicating the name of the Wear OS package that hosts the Media Controls UI. + * + * @hide + */ + public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package"; + + /** + * Setting indicating the name of the Wear OS package responsible for bridging media. + * + * @hide + */ + public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package"; } } 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/view/Choreographer.java b/core/java/android/view/Choreographer.java index c92b1b8c120d..8c4e90c81147 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -195,7 +195,7 @@ public final class Choreographer { private boolean mDebugPrintNextFrameTimeDelta; private int mFPSDivisor = 1; - private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData = + private DisplayEventReceiver.VsyncEventData mLastVsyncEventData = new DisplayEventReceiver.VsyncEventData(); private final FrameData mFrameData = new FrameData(); @@ -857,7 +857,7 @@ public final class Choreographer { mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData.copyFrom(vsyncEventData); + mLastVsyncEventData = vsyncEventData; } AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); @@ -1247,7 +1247,7 @@ public final class Choreographer { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; - private final VsyncEventData mLastVsyncEventData = new VsyncEventData(); + private VsyncEventData mLastVsyncEventData = new VsyncEventData(); FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) { super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle); @@ -1287,7 +1287,7 @@ public final class Choreographer { mTimestampNanos = timestampNanos; mFrame = frame; - mLastVsyncEventData.copyFrom(vsyncEventData); + mLastVsyncEventData = vsyncEventData; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 54db34e788e9..03074894b2ff 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -81,10 +81,7 @@ public abstract class DisplayEventReceiver { // GC'd while the native peer of the receiver is using them. private MessageQueue mMessageQueue; - private final VsyncEventData mVsyncEventData = new VsyncEventData(); - private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver, - WeakReference<VsyncEventData> vsyncEventData, MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle); private static native long nativeGetDisplayEventReceiverFinalizer(); @FastNative @@ -127,9 +124,7 @@ public abstract class DisplayEventReceiver { } mMessageQueue = looper.getQueue(); - mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), - new WeakReference<VsyncEventData>(mVsyncEventData), - mMessageQueue, + mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource, eventRegistration, layerHandle); mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this, mReceiverPtr); @@ -152,6 +147,9 @@ public abstract class DisplayEventReceiver { * @hide */ public static final class VsyncEventData { + static final FrameTimeline[] INVALID_FRAME_TIMELINES = + {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; + // The amount of frame timeline choices. // Must be in sync with VsyncEventData::kFrameTimelinesLength in // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime @@ -159,32 +157,22 @@ public abstract class DisplayEventReceiver { static final int FRAME_TIMELINES_LENGTH = 7; public static class FrameTimeline { - FrameTimeline() {} - - // Called from native code. - @SuppressWarnings("unused") FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) { this.vsyncId = vsyncId; this.expectedPresentationTime = expectedPresentationTime; this.deadline = deadline; } - void copyFrom(FrameTimeline other) { - vsyncId = other.vsyncId; - expectedPresentationTime = other.expectedPresentationTime; - deadline = other.deadline; - } - // The frame timeline vsync id, used to correlate a frame // produced by HWUI with the timeline data stored in Surface Flinger. - public long vsyncId = FrameInfo.INVALID_VSYNC_ID; + public final long vsyncId; // The frame timestamp for when the frame is expected to be presented. - public long expectedPresentationTime = Long.MAX_VALUE; + public final long expectedPresentationTime; // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is // allotted for the frame to be completed. - public long deadline = Long.MAX_VALUE; + public final long deadline; } /** @@ -192,18 +180,11 @@ public abstract class DisplayEventReceiver { * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily * delayed by the app. */ - public long frameInterval = -1; + public final long frameInterval; public final FrameTimeline[] frameTimelines; - public int preferredFrameTimelineIndex = 0; - - VsyncEventData() { - frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH]; - for (int i = 0; i < frameTimelines.length; i++) { - frameTimelines[i] = new FrameTimeline(); - } - } + public final int preferredFrameTimelineIndex; // Called from native code. @SuppressWarnings("unused") @@ -214,12 +195,10 @@ public abstract class DisplayEventReceiver { this.frameInterval = frameInterval; } - void copyFrom(VsyncEventData other) { - preferredFrameTimelineIndex = other.preferredFrameTimelineIndex; - frameInterval = other.frameInterval; - for (int i = 0; i < frameTimelines.length; i++) { - frameTimelines[i].copyFrom(other.frameTimelines[i]); - } + VsyncEventData() { + this.frameInterval = -1; + this.frameTimelines = INVALID_FRAME_TIMELINES; + this.preferredFrameTimelineIndex = 0; } public FrameTimeline preferredFrameTimeline() { @@ -325,8 +304,9 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") - private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) { - onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData); + private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, + VsyncEventData vsyncEventData) { + onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); } // Called from native code. diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index d35aff9a72b7..3812d37a5fed 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -215,6 +215,7 @@ public final class InputWindowHandle { .append(", scaleFactor=").append(scaleFactor) .append(", transform=").append(transform) .append(", windowToken=").append(windowToken) + .append(", displayId=").append(displayId) .append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0) .toString(); diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index ac50d09cc091..cd89a561074c 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -99,9 +99,16 @@ public class SurfaceControlViewHost { @Override public ISurfaceSyncGroup getSurfaceSyncGroup() { CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>(); - mViewRoot.mHandler.post( - () -> surfaceSyncGroup.complete( - mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup)); + // If the call came from in process and it's already running on the UI thread, return + // results immediately instead of posting to the main thread. If we post to the main + // thread, it will block itself and the return value will always be null. + if (Thread.currentThread() == mViewRoot.mThread) { + return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup; + } else { + mViewRoot.mHandler.post( + () -> surfaceSyncGroup.complete( + mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup)); + } try { return surfaceSyncGroup.get(1, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { 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/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/widget/Editor.java b/core/java/android/widget/Editor.java index 088065d2f77d..7931d1a06d39 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -8095,12 +8095,14 @@ public class Editor { private boolean mIsInsertModeActive; private InsertModeTransformationMethod mInsertModeTransformationMethod; private final Paint mHighlightPaint; + private final Path mHighlightPath; InsertModeController(@NonNull TextView textView) { mTextView = Objects.requireNonNull(textView); mIsInsertModeActive = false; mInsertModeTransformationMethod = null; mHighlightPaint = new Paint(); + mHighlightPath = new Path(); // The highlight color is supposed to be 12% of the color primary40. We can't // directly access Material 3 theme. But because Material 3 sets the colorPrimary to @@ -8168,10 +8170,8 @@ public class Editor { ((InsertModeTransformationMethod.TransformedText) transformedText); final int highlightStart = insertModeTransformedText.getHighlightStart(); final int highlightEnd = insertModeTransformedText.getHighlightEnd(); - final Layout.SelectionRectangleConsumer consumer = - (left, top, right, bottom, textSelectionLayout) -> - canvas.drawRect(left, top, right, bottom, mHighlightPaint); - layout.getSelection(highlightStart, highlightEnd, consumer); + layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath); + canvas.drawPath(mHighlightPath, mHighlightPaint); } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 18874f768929..3165654d806d 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1780,6 +1780,21 @@ public class RemoteViews implements Parcelable, Filter { Object value = getParameterValue(view); try { MethodHandle method = getMethod(view, this.methodName, param, true /* async */); + // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon. + // Since bitmaps in framework are seldomly modified, this is supposed to accelerate + // the operations. + if (value instanceof Bitmap bitmap) { + bitmap.prepareToDraw(); + } + + if (value instanceof Icon icon + && (icon.getType() == Icon.TYPE_BITMAP + || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { + Bitmap bitmap = icon.getBitmap(); + if (bitmap != null) { + bitmap.prepareToDraw(); + } + } if (method != null) { Runnable endAction = (Runnable) method.invoke(view, value); diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index e47c335f9c7e..73914a2a99f6 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -16,7 +16,6 @@ package com.android.internal.app; -import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -87,17 +86,13 @@ public class UnlaunchableAppActivity extends Activity mTelecomManager.getDefaultDialerPackage(UserHandle.of(mUserId)))); final AlertDialog.Builder builder; - final String dialogMessage; if (showEmergencyCallButton) { builder = new AlertDialog.Builder(this, R.style.AlertDialogWithEmergencyButton); - dialogMessage = getDialogMessage(R.string.work_mode_dialer_off_message); builder.setNeutralButton(R.string.work_mode_emergency_call_button, this); } else { builder = new AlertDialog.Builder(this); - dialogMessage = getDialogMessage(R.string.work_mode_off_message); } builder.setTitle(getDialogTitle()) - .setMessage(dialogMessage) .setOnDismissListener(this) .setPositiveButton(R.string.work_mode_turn_on, this) .setNegativeButton(R.string.cancel, null); @@ -120,12 +115,6 @@ public class UnlaunchableAppActivity extends Activity UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title)); } - private String getDialogMessage(int dialogMessageString) { - return getSystemService(DevicePolicyManager.class).getResources().getString( - UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, - () -> getString(dialogMessageString)); - } - @Override public void onDismiss(DialogInterface dialog) { finish(); diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index 410b44161cf6..dd72689206ba 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,22 +48,12 @@ static struct { struct { jclass clazz; - jmethodID init; - - jfieldID vsyncId; - jfieldID expectedPresentationTime; - jfieldID deadline; } frameTimelineClassInfo; struct { jclass clazz; - jmethodID init; - - jfieldID frameInterval; - jfieldID preferredFrameTimelineIndex; - jfieldID frameTimelines; } vsyncEventDataClassInfo; } gDisplayEventReceiverClassInfo; @@ -71,7 +61,7 @@ static struct { class NativeDisplayEventReceiver : public DisplayEventDispatcher { public: - NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak, + NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle); @@ -82,7 +72,6 @@ protected: private: jobject mReceiverWeakGlobal; - jobject mVsyncEventDataWeakGlobal; sp<MessageQueue> mMessageQueue; void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, @@ -96,7 +85,6 @@ private: }; NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, - jobject vsyncEventDataWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle) @@ -108,7 +96,6 @@ NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject rece reinterpret_cast<IBinder*>(layerHandle)) : nullptr), mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), - mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)), mMessageQueue(messageQueue) { ALOGV("receiver %p ~ Initializing display event receiver.", this); } @@ -167,43 +154,12 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal)); - ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal)); - if (receiverObj.get() && vsyncEventDataObj.get()) { + if (receiverObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); - env->SetIntField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo - .preferredFrameTimelineIndex, - vsyncEventData.preferredFrameTimelineIndex); - env->SetLongField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval, - vsyncEventData.frameInterval); - - ScopedLocalRef<jobjectArray> - frameTimelinesObj(env, - reinterpret_cast<jobjectArray>( - env->GetObjectField(vsyncEventDataObj.get(), - gDisplayEventReceiverClassInfo - .vsyncEventDataClassInfo - .frameTimelines))); - for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { - VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i]; - ScopedLocalRef<jobject> - frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i)); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId, - frameTimeline.vsyncId); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo - .expectedPresentationTime, - frameTimeline.expectedPresentationTime); - env->SetLongField(frameTimelineObj.get(), - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline, - frameTimeline.deadlineTimestamp); - } - + jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData); env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count); + timestamp, displayId.value, count, javaVsyncEventData); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -271,9 +227,8 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides( mMessageQueue->raiseAndClearException(env, "dispatchModeChanged"); } -static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak, - jobject messageQueueObj, jint vsyncSource, jint eventRegistration, - jlong layerHandle) { +static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj, + jint vsyncSource, jint eventRegistration, jlong layerHandle) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); @@ -281,8 +236,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject } sp<NativeDisplayEventReceiver> receiver = - new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue, - vsyncSource, eventRegistration, layerHandle); + new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource, + eventRegistration, layerHandle); status_t status = receiver->initialize(); if (status) { String8 message; @@ -329,9 +284,7 @@ static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong re static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativeInit", - "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/" - "MessageQueue;IIJ)J", + {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J", (void*)nativeInit}, {"nativeGetDisplayEventReceiverFinalizer", "()J", (void*)nativeGetDisplayEventReceiverFinalizer}, @@ -348,7 +301,8 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); gDisplayEventReceiverClassInfo.dispatchVsync = - GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V"); + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", + "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -374,15 +328,6 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, "<init>", "(JJJ)V"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "vsyncId", "J"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "expectedPresentationTime", "J"); - gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, - "deadline", "J"); jclass vsyncEventDataClazz = FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); @@ -394,17 +339,6 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { "([Landroid/view/" "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "preferredFrameTimelineIndex", "I"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "frameInterval", "J"); - gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines = - GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, - "frameTimelines", - "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;"); - return res; } 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/values/config.xml b/core/res/res/values/config.xml index 3ff63519572f..e4d74b5373e0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -6067,6 +6067,12 @@ <!-- Wear OS: the name of the main activity of the device's sysui. --> <string name="config_wearSysUiMainActivity" translatable="false"/> + <!-- Wear OS: the name of the package containing the Media Controls Activity. --> + <string name="config_wearMediaControlsPackage" translatable="false"/> + + <!-- Wear OS: the name of the package containing the Media Sessions APK. --> + <string name="config_wearMediaSessionsPackage" translatable="false"/> + <bool name="config_secondaryBuiltInDisplayIsRound">@bool/config_windowIsRound</bool> <!-- The display round config for each display in a multi-display device. --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3ee8af2842bf..82314072fbac 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5369,15 +5369,11 @@ <string name="app_suspended_unsuspend_message">Unpause app</string> <!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] --> - <string name="work_mode_off_title">Turn on work apps?</string> - <!-- Text in a dialog. This text is confirming that the user wants to turn on access to their work apps and notifications, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=NONE] --> - <string name="work_mode_off_message">Get access to your work apps and notifications</string> - <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] --> - <string name="work_mode_turn_on">Turn on</string> + <string name="work_mode_off_title">Unpause work apps?</string> + <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] --> + <string name="work_mode_turn_on">Unpause</string> <!-- Title for button to launch the personal safety app to make an emergency call --> <string name="work_mode_emergency_call_button">Emergency</string> - <!-- Text shown in a dialog when the user tries to launch a disabled work profile app when work apps are paused--> - <string name="work_mode_dialer_off_message">Get access to your work apps and calls</string> <!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] --> <string name="app_blocked_title">App is not available</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7b582da836aa..c6c1c8f120d7 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3138,7 +3138,6 @@ <!-- Work profile unlaunchable app alert dialog--> <java-symbol type="style" name="AlertDialogWithEmergencyButton"/> - <java-symbol type="string" name="work_mode_dialer_off_message" /> <java-symbol type="string" name="work_mode_emergency_call_button" /> <java-symbol type="string" name="work_mode_off_title" /> <java-symbol type="string" name="work_mode_off_message" /> @@ -4865,6 +4864,8 @@ <java-symbol type="string" name="config_wearSysUiPackage"/> <java-symbol type="string" name="config_wearSysUiMainActivity"/> + <java-symbol type="string" name="config_wearMediaControlsPackage"/> + <java-symbol type="string" name="config_wearMediaSessionsPackage"/> <java-symbol type="string" name="config_defaultQrCodeComponent"/> <java-symbol type="dimen" name="secondary_rounded_corner_radius" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 2afd54b984a4..0eb4caaf7a0f 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -475,6 +475,18 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RecentTasks.java" }, + "-1643780158": { + "message": "Saving original orientation before camera compat, last orientation is %d", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" + }, + "-1639406696": { + "message": "NOSENSOR override detected", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "-1638958146": { "message": "Removing activity %s from task=%s adding to task=%s Callers=%s", "level": "INFO", @@ -751,6 +763,12 @@ "group": "WM_DEBUG_IME", "at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java" }, + "-1397175017": { + "message": "Other orientation overrides are in place: not reverting", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "-1394745488": { "message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", "level": "INFO", @@ -883,6 +901,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowSurfaceController.java" }, + "-1258739769": { + "message": "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b", + "level": "DEBUG", + "group": "WM_DEBUG_BACK_PREVIEW", + "at": "com\/android\/server\/wm\/BackNavigationController.java" + }, "-1257821162": { "message": "OUT SURFACE %s: copied", "level": "INFO", @@ -1297,6 +1321,12 @@ "group": "WM_DEBUG_BOOT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-874087484": { + "message": "SyncGroup %d: Set ready %b", + "level": "VERBOSE", + "group": "WM_DEBUG_SYNC_ENGINE", + "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" + }, "-869242375": { "message": "Content Recording: Unable to start recording due to invalid region for display %d", "level": "VERBOSE", @@ -1699,6 +1729,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-529187878": { + "message": "Reverting orientation after camera compat force rotation", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" + }, "-521613870": { "message": "App died during pause, not stopping: %s", "level": "VERBOSE", @@ -2371,6 +2407,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "138097009": { + "message": "NOSENSOR override is absent: reverting", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java" + }, "140319294": { "message": "IME target changed within ActivityRecord", "level": "DEBUG", @@ -4003,12 +4045,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "1689989893": { - "message": "SyncGroup %d: Set ready", - "level": "VERBOSE", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "1699269281": { "message": "Don't organize or trigger events for untrusted displayId=%d", "level": "WARN", diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 8dd23b70ae61..25b074d20b81 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1921,6 +1921,7 @@ public final class Bitmap implements Parcelable { */ public void setGainmap(@Nullable Gainmap gainmap) { checkRecycled("Bitmap is recycled"); + mGainmap = null; nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr); } diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 701e20c499da..1da8e189d768 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -482,7 +482,9 @@ public class BitmapFactory { if (opts == null || opts.inBitmap == null) { return 0; } - + // Clear out the gainmap since we don't attempt to reuse it and don't want to + // accidentally keep it on the re-used bitmap + opts.inBitmap.setGainmap(null); return opts.inBitmap.getNativeInstance(); } diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java index d785c3c895b8..f26b50ed4e2a 100644 --- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java +++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java @@ -21,10 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; -import android.os.RemoteException; import android.os.ServiceManager; -import android.security.GenerateRkpKey; -import android.security.keymaster.KeymasterDefs; class CredstoreIdentityCredentialStore extends IdentityCredentialStore { @@ -125,18 +122,7 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore { @NonNull String docType) throws AlreadyPersonalizedException, DocTypeNotSupportedException { try { - IWritableCredential wc; - wc = mStore.createCredential(credentialName, docType); - try { - GenerateRkpKey keyGen = new GenerateRkpKey(mContext); - // We don't know what the security level is for the backing keymint, so go ahead and - // poke the provisioner for both TEE and SB. - keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT); - keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX); - } catch (RemoteException e) { - // Not really an error state. Does not apply at all if RKP is unsupported or - // disabled on a given device. - } + IWritableCredential wc = mStore.createCredential(credentialName, docType); return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc); } catch (android.os.RemoteException e) { throw new RuntimeException("Unexpected RemoteException ", e); diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java deleted file mode 100644 index 698133287f63..000000000000 --- a/keystore/java/android/security/GenerateRkpKey.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (C) 2021 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.security; - -import android.annotation.CheckResult; -import android.annotation.IntDef; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -/** - * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner - * app. There are two cases where Keystore should use this class. - * - * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the - * RemoteProvisioner app check if the state of the attestation key pool is getting low enough - * to warrant provisioning more attestation certificates early. - * - * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of - * attestation key pairs and cannot provide one for the given application. Keystore can then - * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another - * attestation certificate chain provisioned. - * - * In most cases, the proper usage of (1) should preclude the need for (2). - * - * @hide - */ -public class GenerateRkpKey { - private static final String TAG = "GenerateRkpKey"; - - private static final int NOTIFY_EMPTY = 0; - private static final int NOTIFY_KEY_GENERATED = 1; - private static final int TIMEOUT_MS = 1000; - - private IGenerateRkpKeyService mBinder; - private Context mContext; - private CountDownLatch mCountDownLatch; - - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(flag = true, value = { - IGenerateRkpKeyService.Status.OK, - IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY, - IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR, - IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED, - IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR, - IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR, - IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR, - IGenerateRkpKeyService.Status.INTERNAL_ERROR, - }) - public @interface Status { - } - - private ServiceConnection mConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - mBinder = IGenerateRkpKeyService.Stub.asInterface(service); - mCountDownLatch.countDown(); - } - - @Override public void onBindingDied(ComponentName className) { - mCountDownLatch.countDown(); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - mBinder = null; - } - }; - - /** - * Constructor which takes a Context object. - */ - public GenerateRkpKey(Context context) { - mContext = context; - } - - @Status - private int bindAndSendCommand(int command, int securityLevel) throws RemoteException { - Intent intent = new Intent(IGenerateRkpKeyService.class.getName()); - ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); - int returnCode = IGenerateRkpKeyService.Status.OK; - if (comp == null) { - // On a system that does not use RKP, the RemoteProvisioner app won't be installed. - return returnCode; - } - intent.setComponent(comp); - mCountDownLatch = new CountDownLatch(1); - Executor executor = Executors.newCachedThreadPool(); - if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) { - throw new RemoteException("Failed to bind to GenerateRkpKeyService"); - } - try { - mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Log.e(TAG, "Interrupted: ", e); - } - if (mBinder != null) { - switch (command) { - case NOTIFY_EMPTY: - returnCode = mBinder.generateKey(securityLevel); - break; - case NOTIFY_KEY_GENERATED: - mBinder.notifyKeyGenerated(securityLevel); - break; - default: - Log.e(TAG, "Invalid case for command"); - } - } else { - Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService."); - returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR; - } - mContext.unbindService(mConnection); - return returnCode; - } - - /** - * Fulfills the use case of (2) described in the class documentation. Blocks until the - * RemoteProvisioner application can get new attestation keys signed by the server. - * @return the status of the key generation - */ - @CheckResult - @Status - public int notifyEmpty(int securityLevel) throws RemoteException { - return bindAndSendCommand(NOTIFY_EMPTY, securityLevel); - } - - /** - * Fulfills the use case of (1) described in the class documentation. Non blocking call. - */ - public void notifyKeyGenerated(int securityLevel) throws RemoteException { - bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel); - } -} diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl deleted file mode 100644 index eeaeb27a7c77..000000000000 --- a/keystore/java/android/security/IGenerateRkpKeyService.aidl +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (C) 2021 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.security; - -/** - * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This - * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an - * attestation request. The framework can then synchronously call generateKey() to get more - * attestation keys generated and signed. Upon return, the caller can be certain an attestation key - * is available. - * - * @hide - */ -interface IGenerateRkpKeyService { - @JavaDerive(toString=true) - @Backing(type="int") - enum Status { - /** No error(s) occurred */ - OK = 0, - /** Unable to provision keys due to a lack of internet connectivity. */ - NO_NETWORK_CONNECTIVITY = 1, - /** An error occurred while communicating with the RKP server. */ - NETWORK_COMMUNICATION_ERROR = 2, - /** The given device was not registered with the RKP backend. */ - DEVICE_NOT_REGISTERED = 4, - /** The RKP server returned an HTTP client error, indicating a misbehaving client. */ - HTTP_CLIENT_ERROR = 5, - /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */ - HTTP_SERVER_ERROR = 6, - /** The RKP server returned an HTTP status that is unknown. This should never happen. */ - HTTP_UNKNOWN_ERROR = 7, - /** An unexpected internal error occurred. This should never happen. */ - INTERNAL_ERROR = 8, - } - - /** - * Ping the provisioner service to let it know an app generated a key. This may or may not have - * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check. - */ - oneway void notifyKeyGenerated(in int securityLevel); - - /** - * Ping the provisioner service to indicate there are no remaining attestation keys left. - */ - Status generateKey(in int securityLevel); -} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index c3b0f9bc16d3..474b7ea56be9 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -20,7 +20,6 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityThread; import android.content.Context; import android.hardware.security.keymint.EcCurve; import android.hardware.security.keymint.KeyParameter; @@ -28,9 +27,6 @@ import android.hardware.security.keymint.KeyPurpose; import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; -import android.os.RemoteException; -import android.security.GenerateRkpKey; -import android.security.IGenerateRkpKeyService; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore2; import android.security.KeyStoreException; @@ -621,45 +617,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato @Override public KeyPair generateKeyPair() { - GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null); - for (int i = 0; i < 2; i++) { - /** - * NOTE: There is no need to delay between re-tries because the call to - * GenerateRkpKey.notifyEmpty() will delay for a while before returning. - */ - result = generateKeyPairHelper(); - if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) { - return result.keyPair; - } - } - - // RKP failure - if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) { - KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS, - "Could not get RKP keys", result.rkpStatus); - throw new ProviderException("Failed to provision new attestation keys.", ksException); - } - - return result.keyPair; - } - - private static class GenerateKeyPairHelperResult { - // Zero indicates success, non-zero indicates failure. Values should be - // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE}, - // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE}, - // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY} - // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT} - public final int rkpStatus; - @Nullable - public final KeyPair keyPair; - - private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) { - this.rkpStatus = rkpStatus; - this.keyPair = keyPair; - } - } - - private GenerateKeyPairHelperResult generateKeyPairHelper() { if (mKeyStore == null || mSpec == null) { throw new IllegalStateException("Not initialized"); } @@ -697,26 +654,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato AndroidKeyStorePublicKey publicKey = AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse( descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm); - GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread - .currentApplication()); - try { - if (mSpec.getAttestationChallenge() != null) { - keyGen.notifyKeyGenerated(securityLevel); - } - } catch (RemoteException e) { - // This is not really an error state, and necessarily does not apply to non RKP - // systems or hybrid systems where RKP is not currently turned on. - Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e); - } success = true; - KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey()); - return new GenerateKeyPairHelperResult(0, kp); + return new KeyPair(publicKey, publicKey.getPrivateKey()); } catch (KeyStoreException e) { switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: throw new StrongBoxUnavailableException("Failed to generated key pair.", e); - case ResponseCode.OUT_OF_KEYS: - return checkIfRetryableOrThrow(e, securityLevel); default: ProviderException p = new ProviderException("Failed to generate key pair.", e); if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { @@ -742,55 +685,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato } } - // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision - // some keys. - GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) { - GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread - .currentApplication()); - KeyStoreException ksException; - try { - final int keyGenStatus = keyGen.notifyEmpty(securityLevel); - // Default stance: temporary error. This is a hint to the caller to try again with - // exponential back-off. - int rkpStatus; - switch (keyGenStatus) { - case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY: - rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY; - break; - case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED: - rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE; - break; - case IGenerateRkpKeyService.Status.OK: - // Explicitly return not-OK here so we retry in generateKeyPair. All other cases - // should throw because a retry doesn't make sense if we didn't actually - // provision fresh keys. - return new GenerateKeyPairHelperResult( - KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null); - case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR: - case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR: - case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR: - case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR: - case IGenerateRkpKeyService.Status.INTERNAL_ERROR: - default: - // These errors really should never happen. The best we can do is assume they - // are transient and hint to the caller to retry with back-off. - rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE; - break; - } - ksException = new KeyStoreException( - ResponseCode.OUT_OF_KEYS, - "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus, - rkpStatus); - } catch (RemoteException f) { - ksException = new KeyStoreException( - ResponseCode.OUT_OF_KEYS, - "Remote exception: " + f.getMessage(), - KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE); - } - ksException.initCause(e); - throw new ProviderException("Failed to provision new attestation keys.", ksException); - } - private void addAttestationParameters(@NonNull List<KeyParameter> params) throws ProviderException, IllegalArgumentException, DeviceIdAttestationException { byte[] challenge = mSpec.getAttestationChallenge(); 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/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java index a7171fd5b220..82fe38ccc7b3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java @@ -21,6 +21,7 @@ import static com.android.wm.shell.pip.PipUtils.dpToPx; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; @@ -366,11 +367,8 @@ public class PipSizeSpecHandler { mContext = context; mPipDisplayLayoutState = pipDisplayLayoutState; - boolean enablePipSizeLargeScreen = SystemProperties - .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true); - // choose between two implementations of size spec logic - if (enablePipSizeLargeScreen) { + if (supportsPipSizeLargeScreen()) { mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl(); } else { mSizeSpecSourceImpl = new SizeSpecDefaultImpl(); @@ -515,6 +513,18 @@ public class PipSizeSpecHandler { } } + @VisibleForTesting + boolean supportsPipSizeLargeScreen() { + // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource + // can be injected + return SystemProperties + .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv(); + } + + private boolean isTv() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + /** Dumps internal state. */ public void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; 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..04cb17c6a735 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 @@ -690,6 +690,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { // Launching a solo task. + // Exit split first if this task under split roots. + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); + } ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); options1 = activityOptions.toBundle(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index f99821747fef..afc573e4fcbb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -197,7 +197,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { - if (mTransitionPausingRelayout.equals(merged)) { + if (merged.equals(mTransitionPausingRelayout)) { mTransitionPausingRelayout = playing; } } @@ -312,8 +312,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } else if (id == R.id.back_button) { mTaskOperations.injectBackKey(); } else if (id == R.id.caption_handle || id == R.id.open_menu_button) { - moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); - decoration.createHandleMenu(); + if (!decoration.isHandleMenuActive()) { + moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId)); + decoration.createHandleMenu(); + } else { + decoration.closeHandleMenu(); + } } else if (id == R.id.desktop_button) { mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true)); mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index efc90b5e63e1..f9c0e600dd38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -487,6 +487,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return; PointF inputPoint = offsetCaptionLocation(ev); + + // If this is called before open_menu_button's onClick, we don't want to close + // the menu since it will just reopen in onClick. + final boolean pointInOpenMenuButton = pointInView( + mResult.mRootView.findViewById(R.id.open_menu_button), + inputPoint.x, + inputPoint.y); + final boolean pointInAppInfoPill = pointInView( mHandleMenuAppInfoPill.mWindowViewHost.getView(), inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX, @@ -506,7 +514,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin - mResult.mDecorContainerOffsetX, inputPoint.y - mHandleMenuMoreActionsPillPosition.y - mResult.mDecorContainerOffsetY); - if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill) { + if (!pointInAppInfoPill && !pointInWindowingPill + && !pointInMoreActionsPill && !pointInOpenMenuButton) { closeHandleMenu(); } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index e986ee127708..c416ad011c4e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -137,7 +137,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible( portraitPosTop: Boolean ) { assertLayers { - this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) + this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true) .then() .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component)) .then() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java index 390c830069eb..425bbf056b85 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java @@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -76,7 +75,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { @Mock private Resources mResources; private PipDisplayLayoutState mPipDisplayLayoutState; - private PipSizeSpecHandler mPipSizeSpecHandler; + private TestPipSizeSpecHandler mPipSizeSpecHandler; /** * Sets up static Mockito session for SystemProperties and mocks necessary static methods. @@ -84,8 +83,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { private static void setUpStaticSystemPropertiesSession() { sStaticMockitoSession = mockitoSession() .mockStatic(SystemProperties.class).startMocking(); - // make sure the feature flag is on - when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true); when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> { String property = invocation.getArgument(0); if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) { @@ -161,7 +158,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { mPipDisplayLayoutState.setDisplayLayout(displayLayout); setUpStaticSystemPropertiesSession(); - mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState); + mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState); // no overridden min edge size by default mPipSizeSpecHandler.setOverrideMinSize(null); @@ -214,4 +211,16 @@ public class PipSizeSpecHandlerTest extends ShellTestCase { Assert.assertEquals(expectedSize, actualSize); } + + static class TestPipSizeSpecHandler extends PipSizeSpecHandler { + + TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) { + super(context, displayLayoutState); + } + + @Override + boolean supportsPipSizeLargeScreen() { + return true; + } + } } diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 0b58406516e3..924fbd659824 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -51,6 +51,9 @@ #include "include/gpu/GrDirectContext.h" #include "pipeline/skia/AnimatedDrawables.h" #include "pipeline/skia/FunctorDrawable.h" +#ifdef __ANDROID__ +#include "renderthread/CanvasContext.h" +#endif namespace android { namespace uirenderer { @@ -489,7 +492,19 @@ struct DrawPoints final : Op { size_t count; SkPaint paint; void draw(SkCanvas* c, const SkMatrix&) const { - c->drawPoints(mode, count, pod<SkPoint>(this), paint); + if (paint.isAntiAlias()) { + c->drawPoints(mode, count, pod<SkPoint>(this), paint); + } else { + c->save(); +#ifdef __ANDROID__ + auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix(); + auto transform = c->getLocalToDevice(); + transform.postConcat(pixelSnap); + c->setMatrix(transform); +#endif + c->drawPoints(mode, count, pod<SkPoint>(this), paint); + c->restore(); + } } }; struct DrawVertices final : Op { diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index d08bc5c583c2..8049dc946c9e 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -29,9 +29,10 @@ namespace android { -AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed) - : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) { - mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration()); +AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format) + : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) { + mTimeToShowNextSnapshot = ms2ns(currentFrameDuration()); setStagingBounds(mSkAnimatedImage->getBounds()); } @@ -92,7 +93,7 @@ bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) { // directly from mSkAnimatedImage. lock.unlock(); std::unique_lock imageLock{mImageLock}; - *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration()); + *outDelay = ms2ns(currentFrameDuration()); return true; } else { // The next snapshot has not yet been decoded, but we've already passed @@ -109,7 +110,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { Snapshot snap; { std::unique_lock lock{mImageLock}; - snap.mDurationMS = mSkAnimatedImage->decodeNextFrame(); + snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } @@ -123,7 +124,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); - snap.mDurationMS = mSkAnimatedImage->currentFrameDuration(); + snap.mDurationMS = currentFrameDuration(); } return snap; @@ -274,7 +275,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; mSkAnimatedImage->reset(); - durationMS = mSkAnimatedImage->currentFrameDuration(); + durationMS = currentFrameDuration(); } { std::unique_lock lock{mSwapLock}; @@ -306,7 +307,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { { std::unique_lock lock{mImageLock}; if (update) { - durationMS = mSkAnimatedImage->decodeNextFrame(); + durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame()); } canvas->drawDrawable(mSkAnimatedImage.get()); @@ -336,4 +337,20 @@ SkRect AnimatedImageDrawable::onGetBounds() { return SkRectMakeLargest(); } +int AnimatedImageDrawable::adjustFrameDuration(int durationMs) { + if (durationMs == SkAnimatedImage::kFinished) { + return SkAnimatedImage::kFinished; + } + + if (mFormat == SkEncodedImageFormat::kGIF) { + // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms + return durationMs <= 10 ? 100 : durationMs; + } + return durationMs; +} + +int AnimatedImageDrawable::currentFrameDuration() { + return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration()); +} + } // namespace android diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index 8ca3c7e125f1..1e965abc82b5 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -16,16 +16,16 @@ #pragma once -#include <cutils/compiler.h> -#include <utils/Macros.h> -#include <utils/RefBase.h> -#include <utils/Timers.h> - #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> +#include <SkEncodedImageFormat.h> #include <SkPicture.h> +#include <cutils/compiler.h> +#include <utils/Macros.h> +#include <utils/RefBase.h> +#include <utils/Timers.h> #include <future> #include <mutex> @@ -48,7 +48,8 @@ class AnimatedImageDrawable : public SkDrawable { public: // bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the // Snapshots. - AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed); + AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed, + SkEncodedImageFormat format); /** * This updates the internal time and returns true if the image needs @@ -115,6 +116,7 @@ protected: private: sk_sp<SkAnimatedImage> mSkAnimatedImage; const size_t mBytesUsed; + const SkEncodedImageFormat mFormat; bool mRunning = false; bool mStarting = false; @@ -157,6 +159,9 @@ private: Properties mProperties; std::unique_ptr<OnAnimationEndListener> mEndListener; + + int adjustFrameDuration(int); + int currentFrameDuration(); }; } // namespace android diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp index 373e893b9a25..a7f5aa83e624 100644 --- a/libs/hwui/jni/AnimatedImageDrawable.cpp +++ b/libs/hwui/jni/AnimatedImageDrawable.cpp @@ -97,7 +97,7 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += picture->approximateBytesUsed(); } - + SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat(); sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec), info, subset, std::move(picture)); @@ -108,8 +108,8 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/, bytesUsed += sizeof(animatedImg.get()); - sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg), - bytesUsed)); + sk_sp<AnimatedImageDrawable> drawable( + new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format)); return reinterpret_cast<jlong>(drawable.release()); } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index cc987bcd8f0e..2a8cb42f7675 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -104,7 +104,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw( GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo); - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag, + kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); sk_sp<SkSurface> surface; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 940d6bfdb83c..f0461bef170c 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -53,6 +53,14 @@ public: bool isSurfaceReady() override; bool isContextReady() override; + const SkM44& getPixelSnapMatrix() const override { + // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the + // desired fragment + static const SkScalar kOffset = 0.063f; + static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset); + return sSnapMatrix; + } + static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); protected: diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 1f929685b62c..b020e966e05a 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -28,7 +28,6 @@ #include <SkMultiPictureDocument.h> #include <SkOverdrawCanvas.h> #include <SkOverdrawColorFilter.h> -#include <SkPaintFilterCanvas.h> #include <SkPicture.h> #include <SkPictureRecorder.h> #include <SkRect.h> @@ -450,23 +449,6 @@ 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, @@ -521,12 +503,6 @@ 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 0763b06b53ef..befee8989383 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -98,8 +98,6 @@ 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 6f1b99b95bbd..86096d5bd01c 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -203,11 +203,6 @@ 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); @@ -215,6 +210,10 @@ void SkiaVulkanPipeline::onContextDestroyed() { } } +const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const { + return mVkSurface->getPixelSnapMatrix(); +} + } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 0713e93bccde..284cde537ec0 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -53,8 +53,8 @@ public: void onStop() override; bool isSurfaceReady() override; bool isContextReady() override; - bool supportsExtendedRangeHdr() const override { return true; } void setTargetSdrHdrRatio(float ratio) override; + const SkM44& getPixelSnapMatrix() const override; static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread, @@ -63,8 +63,6 @@ public: protected: void onContextDestroyed() override; - bool shouldForceDither() const override; - private: renderthread::VulkanManager& vulkanManager(); renderthread::VulkanSurface* mVkSurface = nullptr; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 6b2c99534a4c..f60c1f3c6ad8 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -236,7 +236,6 @@ void CanvasContext::setupPipelineSurface() { if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) { setBufferCount(mNativeSurface->getNativeWindow()); - } mFrameNumber = 0; @@ -301,10 +300,6 @@ void CanvasContext::setOpaque(bool opaque) { float CanvasContext::setColorMode(ColorMode mode) { if (mode != mColorMode) { - const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10; - if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) { - mode = ColorMode::WideColorGamut; - } mColorMode = mode; mRenderPipeline->setSurfaceColorProperties(mode); setupPipelineSurface(); @@ -871,6 +866,10 @@ SkISize CanvasContext::getNextFrameSize() const { return size; } +const SkM44& CanvasContext::getPixelSnapMatrix() const { + return mRenderPipeline->getPixelSnapMatrix(); +} + void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 3f2533959c20..d7215de92375 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -200,6 +200,9 @@ public: SkISize getNextFrameSize() const; + // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center + const SkM44& getPixelSnapMatrix() const; + // Called when SurfaceStats are available. static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId, ASurfaceControlStats* stats); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 4fb114b71bf5..94f35fd9eaf2 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -423,6 +423,7 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE}; EGLConfig config = mEglConfig; + bool overrideWindowDataSpaceForHdr = false; if (colorMode == ColorMode::A8) { // A8 doesn't use a color space if (!mEglConfigA8) { @@ -450,12 +451,13 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, case ColorMode::Default: attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; break; - // Extended Range HDR requires being able to manipulate the dataspace in ways - // we cannot easily do while going through EGLSurface. Given this requires - // composer3 support, just treat HDR as equivalent to wide color gamut if - // the GLES path is still being hit + // We don't have an EGL colorspace for extended range P3 that's used for HDR + // So override it after configuring the EGL context case ColorMode::Hdr: case ColorMode::Hdr10: + overrideWindowDataSpaceForHdr = true; + attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT; + break; case ColorMode::WideColorGamut: { skcms_Matrix3x3 colorGamut; LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut), @@ -491,6 +493,16 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, (void*)window, eglErrorString()); } + if (overrideWindowDataSpaceForHdr) { + // This relies on knowing that EGL will not re-set the dataspace after the call to + // eglCreateWindowSurface. Since the handling of the colorspace extension is largely + // implemented in libEGL in the platform, we can safely assume this is the case + int32_t err = ANativeWindow_setBuffersDataSpace( + window, + static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED)); + LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err); + } + return surface; } diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index c68fcdfc76f2..6c2cb9d71c55 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -95,8 +95,8 @@ public: virtual void setPictureCapturedCallback( const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0; - virtual bool supportsExtendedRangeHdr() const { return false; } virtual void setTargetSdrHdrRatio(float ratio) = 0; + virtual const SkM44& getPixelSnapMatrix() const = 0; virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp index ae4f0572576e..10f456745147 100644 --- a/libs/hwui/renderthread/VulkanSurface.cpp +++ b/libs/hwui/renderthread/VulkanSurface.cpp @@ -63,6 +63,18 @@ static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) { return SkMatrix::I(); } +static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) { + // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the + // desired fragment + static const SkScalar kOffset = 0.063f; + SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform); + SkMatrix invert; + LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert)); + return SkM44::Translate(kOffset, kOffset) + .postConcat(SkM44(preRotation)) + .preConcat(SkM44(invert)); +} + static bool ConnectAndSetWindowDefaults(ANativeWindow* window) { ATRACE_CALL(); @@ -178,6 +190,8 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode outWindowInfo->preTransform = GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform); + outWindowInfo->pixelSnapMatrix = + GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform); err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value); if (err != 0 || query_value < 0) { @@ -413,6 +427,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { } mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform); + mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform); } uint32_t idx; @@ -438,9 +453,15 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() { VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx]; if (bufferInfo->skSurface.get() == nullptr) { + SkSurfaceProps surfaceProps; + if (mWindowInfo.colorMode != ColorMode::Default) { + surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(), + surfaceProps.pixelGeometry()); + } bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer( mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()), - kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true); + kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps, + /*from_window=*/true); if (bufferInfo->skSurface.get() == nullptr) { ALOGE("SkSurface::MakeFromAHardwareBuffer failed"); mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer, @@ -530,16 +551,6 @@ 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 3b69b73bcab3..6f5280105e55 100644 --- a/libs/hwui/renderthread/VulkanSurface.h +++ b/libs/hwui/renderthread/VulkanSurface.h @@ -47,8 +47,7 @@ public: const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; } void setColorSpace(sk_sp<SkColorSpace> colorSpace); - - bool isBeyond8Bit() const; + const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; } private: /* @@ -107,6 +106,7 @@ private: SkISize actualSize; // transform to be applied to the SkSurface to map the coordinates to the provided transform SkMatrix preTransform; + SkM44 pixelSnapMatrix; }; VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext); diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h index c7530207d1fa..060abfdc1ee5 100644 --- a/media/jni/android_media_MediaCodecLinearBlock.h +++ b/media/jni/android_media_MediaCodecLinearBlock.h @@ -44,12 +44,19 @@ struct JMediaCodecLinearBlock { std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) const { if (mBuffer) { + // TODO: if returned C2Buffer is different from mBuffer, we should + // find a way to connect the life cycle between this C2Buffer and + // mBuffer. if (mBuffer->data().type() != C2BufferData::LINEAR) { return nullptr; } C2ConstLinearBlock block = mBuffer->data().linearBlocks().front(); if (offset == 0 && size == block.capacity()) { - return mBuffer; + // Let C2Buffer be new one to queue to MediaCodec. It will allow + // the related input slot to be released by onWorkDone from C2 + // Component. Currently, the life cycle of mBuffer should be + // protected by different flows. + return std::make_shared<C2Buffer>(*mBuffer); } std::shared_ptr<C2Buffer> buffer = diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml index d87abb98ebde..ebfb86d4d6ba 100644 --- a/packages/CompanionDeviceManager/res/values/strings.xml +++ b/packages/CompanionDeviceManager/res/values/strings.xml @@ -34,7 +34,7 @@ <string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string> <!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] --> - <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string> + <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string> <!-- ================= DEVICE_PROFILE_GLASSES ================= --> @@ -48,7 +48,7 @@ <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string> <!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] --> - <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string> + <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string> <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= --> @@ -59,7 +59,7 @@ <string name="helper_title_app_streaming">Cross-device services</string> <!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] --> - <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string> + <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string> <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= --> @@ -81,7 +81,7 @@ <string name="helper_title_computer">Google Play services</string> <!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] --> - <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string> + <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string> <!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= --> diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index e9b2e1041c22..3e652517270d 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -117,6 +117,8 @@ <string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string> <!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] --> <string name="get_dialog_title_sign_in_options">Sign-in options</string> + <!-- Button label for viewing the full information about an account. [CHAR LIMIT=80] --> + <string name="button_label_view_more">View more</string> <!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] --> <string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string> <!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] --> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index dd4419bc6540..e53e956c317c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -65,8 +65,8 @@ class CredentialManagerRepo( ) val originName: String? = when (requestInfo?.type) { - RequestInfo.TYPE_CREATE -> requestInfo?.createCredentialRequest?.origin - RequestInfo.TYPE_GET -> requestInfo?.getCredentialRequest?.origin + RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin + RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin else -> null } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 725401fb85bb..ca891294576b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -210,7 +210,11 @@ class GetFlowUtils { appName = originName ?: getAppLabel(context.packageManager, requestInfo.appPackageName) ?: return null, - preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials + preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials, + preferIdentityDocUi = getCredentialRequest.data.getBoolean( + // TODO(b/276777444): replace with direct library constant reference once + // exposed. + "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"), ) } @@ -241,7 +245,7 @@ class GetFlowUtils { userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon ?: false, + shouldTintIcon = credentialEntry.isDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, isAutoSelectable = credentialEntry.isAutoSelectAllowed && credentialEntry.autoSelectAllowedFromOption, diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt index 307d95313ec8..10a75d436a5b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt @@ -316,7 +316,7 @@ fun ModalBottomSheetLayout( rememberModalBottomSheetState(Hidden), sheetShape: Shape = MaterialTheme.shapes.large, sheetElevation: Dp = ModalBottomSheetDefaults.Elevation, - sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface, + sheetBackgroundColor: Color, sheetContentColor: Color = contentColorFor(sheetBackgroundColor), content: @Composable () -> Unit ) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt index 7a720b1e858b..2dba2ab6777c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt @@ -32,7 +32,6 @@ import androidx.compose.material.icons.outlined.Lock import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SuggestionChip import androidx.compose.material3.SuggestionChipDefaults import androidx.compose.material3.TopAppBar @@ -52,6 +51,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection @@ -80,6 +80,7 @@ fun Entry( /** If true, draws a trailing lock icon. */ isLockedAuthEntry: Boolean = false, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { val iconPadding = Modifier.wrapContentSize().padding( // Horizontal padding should be 16dp, but the suggestion chip itself @@ -104,7 +105,11 @@ fun Entry( ) { // Apply weight so that the trailing icon can always show. Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) { - SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine) + SmallTitleText( + text = entryHeadlineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) if (passwordValue != null) { Row( modifier = Modifier.fillMaxWidth().padding(top = 4.dp), @@ -142,10 +147,18 @@ fun Entry( ) } } else if (entrySecondLineText != null) { - BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entrySecondLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } if (entryThirdLineText != null) { - BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine) + BodySmallText( + text = entryThirdLineText, + enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, + ) } } if (isLockedAuthEntry) { @@ -155,7 +168,7 @@ fun Entry( // Decorative purpose only. contentDescription = null, modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, ) } } @@ -169,7 +182,7 @@ fun Entry( Icon( modifier = iconSize, bitmap = iconImageBitmap, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, // Decorative purpose only. contentDescription = null, ) @@ -193,7 +206,7 @@ fun Entry( Icon( modifier = iconSize, imageVector = iconImageVector, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, // Decorative purpose only. contentDescription = null, ) @@ -205,7 +218,7 @@ fun Entry( Icon( modifier = iconSize, painter = iconPainter, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, // Decorative purpose only. contentDescription = null, ) @@ -217,9 +230,8 @@ fun Entry( border = null, colors = SuggestionChipDefaults.suggestionChipColors( containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh, - // TODO: remove? - labelColor = MaterialTheme.colorScheme.onSurfaceVariant, - iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, + labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant, + iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant, ), ) } @@ -282,7 +294,7 @@ fun PasskeyBenefitRow( Icon( modifier = Modifier.size(24.dp), painter = leadingIconPainter, - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, // Decorative purpose only. contentDescription = null, ) @@ -341,7 +353,7 @@ fun MoreOptionTopAppBar( R.string.accessibility_back_arrow_button ), modifier = Modifier.size(24.dp).autoMirrored(), - tint = MaterialTheme.colorScheme.onSurfaceVariant, + tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt index 358122809985..14bf4f23384b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt @@ -20,20 +20,20 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @Composable fun CredentialListSectionHeader(text: String) { - InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant) + InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant) } @Composable fun MoreAboutPasskeySectionHeader(text: String) { - InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface) + InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface) } @Composable diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt index 22871bcbe767..6b46636964e4 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt @@ -22,8 +22,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme /** * The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X". @@ -35,7 +37,7 @@ fun HeadlineText(text: String, modifier: Modifier = Modifier) { Text( modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.onSurface, + color = LocalAndroidColorScheme.current.colorOnSurface, textAlign = TextAlign.Center, style = MaterialTheme.typography.headlineSmall, ) @@ -49,7 +51,7 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) { Text( modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, style = MaterialTheme.typography.bodyMedium, ) } @@ -58,14 +60,20 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) { * Body-small typography; on-surface-variant color. */ @Composable -fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun BodySmallText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } @@ -77,7 +85,7 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) { Text( modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.onSurface, + color = LocalAndroidColorScheme.current.colorOnSurface, style = MaterialTheme.typography.titleLarge, ) } @@ -86,14 +94,20 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) { * Title-small typography; on-surface color. */ @Composable -fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) { +fun SmallTitleText( + text: String, + modifier: Modifier = Modifier, + enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, +) { Text( modifier = modifier.wrapContentSize(), text = text, - color = MaterialTheme.colorScheme.onSurface, + color = LocalAndroidColorScheme.current.colorOnSurface, style = MaterialTheme.typography.titleSmall, overflow = TextOverflow.Ellipsis, - maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE + maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE, + onTextLayout = onTextLayout, ) } @@ -145,7 +159,7 @@ fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier) modifier = modifier.wrapContentSize(), text = text, textAlign = TextAlign.Center, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, style = MaterialTheme.typography.labelLarge, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 648d83268541..66d7db896247 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.material3.Divider -import androidx.compose.material3.MaterialTheme import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.filled.Add @@ -67,6 +66,7 @@ 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.logging.CreateCredentialEvent +import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme import com.android.internal.logging.UiEventLogger.UiEventEnum @Composable @@ -559,7 +559,7 @@ fun CreationSelectionCard( item { Divider( thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant, + color = LocalAndroidColorScheme.current.colorOutlineVariant, modifier = Modifier.padding(vertical = 16.dp) ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index 6d7ecd70eb0b..c1ea1d8d1746 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -34,11 +34,15 @@ import androidx.compose.material3.Divider import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.CredentialSelectorViewModel @@ -158,6 +162,7 @@ fun PrimarySelectionCard( onMoreOptionSelected: () -> Unit, onLog: @Composable (UiEventEnum) -> Unit, ) { + val showMoreForTruncatedEntry = remember { mutableStateOf(false) } val sortedUserNameToCredentialEntryList = providerDisplayInfo.sortedUserNameToCredentialEntryList val authenticationEntryList = providerDisplayInfo.authenticationEntryList @@ -209,6 +214,8 @@ fun PrimarySelectionCard( Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size val authenticationEntrySize = authenticationEntryList.size + // If true, render a view more button for the single truncated entry on the + // front page. // Show max 4 entries in this primary page if (usernameForCredentialSize + authenticationEntrySize <= 4) { sortedUserNameToCredentialEntryList.forEach { @@ -216,6 +223,9 @@ fun PrimarySelectionCard( credentialEntryInfo = it.sortedCredentialEntryList.first(), onEntrySelected = onEntrySelected, enforceOneLine = true, + onTextLayout = { + showMoreForTruncatedEntry.value = it.hasVisualOverflow + } ) } authenticationEntryList.forEach { @@ -269,6 +279,13 @@ fun PrimarySelectionCard( onMoreOptionSelected ) } + } else if (showMoreForTruncatedEntry.value) { + { + ActionButton( + stringResource(R.string.button_label_view_more), + onMoreOptionSelected + ) + } } else null, rightButton = if (activeEntry != null) { // Only one sign-in options exist { @@ -438,6 +455,7 @@ fun CredentialEntryRow( credentialEntryInfo: CredentialEntryInfo, onEntrySelected: (BaseEntry) -> Unit, enforceOneLine: Boolean = false, + onTextLayout: (TextLayoutResult) -> Unit = {}, ) { Entry( onClick = { onEntrySelected(credentialEntryInfo) }, @@ -463,6 +481,7 @@ fun CredentialEntryRow( ) }, enforceOneLine = enforceOneLine, + onTextLayout = onTextLayout, ) } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 7a8679038579..c9c62a48d130 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -42,7 +42,7 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean { } internal fun isFallbackScreen(state: GetCredentialUiState): Boolean { - return false + return state.requestDisplayInfo.preferIdentityDocUi } internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? { @@ -172,6 +172,7 @@ class ActionEntryInfo( data class RequestDisplayInfo( val appName: String, val preferImmediatelyAvailableCredentials: Boolean, + val preferIdentityDocUi: Boolean, ) /** diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt index 8928e1869838..a33904d30393 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt @@ -42,6 +42,9 @@ val LocalAndroidColorScheme = class AndroidColorScheme internal constructor(context: Context) { val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright) val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh) + val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant) + val colorOnSurface = getColor(context, R.attr.materialColorOnSurface) + val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant) companion object { fun getColor(context: Context, attr: Int): Color { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 47ac2df67c76..e07a6298a627 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -69,6 +69,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp @@ -460,6 +461,9 @@ private fun TopAppBarLayout( ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, + // Disable the title font scaling by only passing the density but not the + // font scale. + LocalDensity provides Density(density = LocalDensity.current.density), content = title ) } diff --git a/packages/SettingsLib/res/drawable/ic_dock_device.xml b/packages/SettingsLib/res/drawable/ic_dock_device.xml new file mode 100644 index 000000000000..96a4900f361f --- /dev/null +++ b/packages/SettingsLib/res/drawable/ic_dock_device.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="#000000" + android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM120,720Q87,720 63.5,696.5Q40,673 40,640L60,160Q60,127 83.5,103.5Q107,80 140,80L820,80Q853,80 876.5,103.5Q900,127 900,160L920,640Q920,673 896.5,696.5Q873,720 840,720L120,720ZM120,640L840,640Q840,640 840,640Q840,640 840,640L820,160Q820,160 820,160Q820,160 820,160L140,160Q140,160 140,160Q140,160 140,160L120,640Q120,640 120,640Q120,640 120,640ZM320,880Q259,880 209.5,850Q160,820 160,765L160,720L240,720L240,760Q253,780 274.5,790Q296,800 320,800L640,800Q664,800 685.5,790.5Q707,781 720,761L720,720L800,720L800,765Q800,820 750.5,850Q701,880 640,880L320,880ZM480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Z"/> +</vector>
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java new file mode 100644 index 000000000000..5326e73a3f82 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.fuelgauge; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Utilities related to battery saver logging. + */ +public final class BatterySaverLogging { + /** + * Record the reason while enabling power save mode manually. + * See {@link SaverManualEnabledReason} for all available states. + */ + public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON = + "extra_power_save_mode_manual_enabled_reason"; + + /** Broadcast action to record battery saver manual enabled reason. */ + public static final String ACTION_SAVER_MANUAL_ENABLED_REASON = + "com.android.settingslib.fuelgauge.ACTION_SAVER_MANUAL_ENABLED_REASON"; + + /** An interface for the battery saver manual enable reason. */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SAVER_ENABLED_UNKNOWN, SAVER_ENABLED_CONFIRMATION, SAVER_ENABLED_VOICE, + SAVER_ENABLED_SETTINGS, SAVER_ENABLED_QS, SAVER_ENABLED_LOW_WARNING, + SAVER_ENABLED_SEVERE_WARNING}) + public @interface SaverManualEnabledReason {} + + public static final int SAVER_ENABLED_UNKNOWN = 0; + public static final int SAVER_ENABLED_CONFIRMATION = 1; + public static final int SAVER_ENABLED_VOICE = 2; + public static final int SAVER_ENABLED_SETTINGS = 3; + public static final int SAVER_ENABLED_QS = 4; + public static final int SAVER_ENABLED_LOW_WARNING = 5; + public static final int SAVER_ENABLED_SEVERE_WARNING = 6; +} diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index 52f3111d967c..e28ada4771c9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -16,6 +16,10 @@ package com.android.settingslib.fuelgauge; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_MANUAL_ENABLED_REASON; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -116,9 +120,9 @@ public class BatterySaverUtils { * @return true if the request succeeded. */ public static synchronized boolean setPowerSaveMode(Context context, - boolean enable, boolean needFirstTimeWarning) { + boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) { if (DEBUG) { - Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF")); + Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason); } final ContentResolver cr = context.getContentResolver(); @@ -145,8 +149,10 @@ public class BatterySaverUtils { && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0 && Secure.getInt(cr, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) { - showAutoBatterySaverSuggestion(context, confirmationExtras); + sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION, + confirmationExtras); } + recordBatterySaverEnabledReason(context, reason); } return true; @@ -175,21 +181,23 @@ public class BatterySaverUtils { // Already shown. return false; } - context.sendBroadcast( - getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras)); + sendSystemUiBroadcast(context, ACTION_SHOW_START_SAVER_CONFIRMATION, extras); return true; } - private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) { - context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras)); + private static void recordBatterySaverEnabledReason(Context context, + @SaverManualEnabledReason int reason) { + final Bundle enabledReasonExtras = new Bundle(1); + enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason); + sendSystemUiBroadcast(context, ACTION_SAVER_MANUAL_ENABLED_REASON, enabledReasonExtras); } - private static Intent getSystemUiBroadcast(String action, Bundle extras) { - final Intent i = new Intent(action); - i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); - i.setPackage(SYSUI_PACKAGE); - i.putExtras(extras); - return i; + private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) { + final Intent intent = new Intent(action); + intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.setPackage(SYSUI_PACKAGE); + intent.putExtras(extras); + context.sendBroadcast(intent); } private static void setBatterySaverConfirmationAcknowledged(Context context) { diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java index 6c0eab3fb016..e38e041a87dc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java @@ -55,7 +55,7 @@ public class DeviceIconUtil { new Device( AudioDeviceInfo.TYPE_DOCK, MediaRoute2Info.TYPE_DOCK, - R.drawable.ic_headphone), + R.drawable.ic_dock_device), new Device( AudioDeviceInfo.TYPE_HDMI, MediaRoute2Info.TYPE_HDMI, diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java index ad022a63eaf6..cb386fbff4ed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java @@ -16,6 +16,7 @@ package com.android.settingslib.fuelgauge; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE; @@ -72,7 +73,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isFalse(); verify(mMockContext, times(1)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean()); @@ -92,7 +94,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -111,7 +114,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -129,7 +133,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -147,7 +152,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); @@ -166,7 +172,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 59cd7a051fad..a93cd62e6301 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -43,6 +43,8 @@ <bool name="def_install_non_market_apps">false</bool> <!-- 0 == off, 3 == on --> <integer name="def_location_mode">3</integer> + <!-- 0 == off, 1 == on--> + <integer name="def_paired_device_location_mode">1</integer> <bool name="assisted_gps_enabled">true</bool> <bool name="def_netstats_enabled">true</bool> <bool name="def_usb_mass_storage_enabled">true</bool> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index e50f52229a16..41ce58eb7b4e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -103,5 +103,7 @@ public class GlobalSettings { Settings.Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS, Settings.Global.HDR_CONVERSION_MODE, Settings.Global.HDR_FORCE_CONVERSION_TYPE, + Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, + Settings.Global.Wearable.REDUCE_MOTION, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index d5386c1d75ac..a1c01723ad55 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -285,7 +285,6 @@ public class GlobalSettingsValidators { })); VALIDATORS.put(Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.SIDE_BUTTON, BOOLEAN_VALIDATOR); - VALIDATORS.put(Global.Wearable.BUTTON_SET, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.ANDROID_WEAR_VERSION, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.SYSTEM_CAPABILITIES, ANY_INTEGER_VALIDATOR); VALIDATORS.put(Global.Wearable.SYSTEM_EDITION, ANY_INTEGER_VALIDATOR); @@ -345,6 +344,7 @@ public class GlobalSettingsValidators { String.valueOf(Global.Wearable.HFP_CLIENT_DISABLED) })); VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR); + VALIDATORS.put(Global.Wearable.COMPANION_APP_NAME, ANY_STRING_VALIDATOR); VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR); VALIDATORS.put( @@ -404,16 +404,6 @@ public class GlobalSettingsValidators { VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR); - VALIDATORS.put( - Global.Wearable.EARLY_UPDATES_STATUS, - new DiscreteValueValidator( - new String[] { - String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_NOT_STARTED), - String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_STARTED), - String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SUCCESS), - String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED), - String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED), - })); VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS, @@ -423,5 +413,22 @@ public class GlobalSettingsValidators { String.valueOf(Global.Wearable.UPGRADE_DATA_MIGRATION_PENDING), String.valueOf(Global.Wearable.UPGRADE_DATA_MIGRATION_DONE) })); + VALIDATORS.put(Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.NETWORK_LOCATION_OPT_IN, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_STATUS, + new InclusiveIntegerRangeValidator( + Global.Wearable.PHONE_SWITCHING_STATUS_NOT_STARTED, + Global.Wearable.PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS)); + VALIDATORS.put(Global.Wearable.REDUCE_MOTION, BOOLEAN_VALIDATOR); + VALIDATORS.put(Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Global.Wearable.TETHER_CONFIG_STATE, + new DiscreteValueValidator( + new String[] { + String.valueOf(Global.Wearable.TETHERED_CONFIG_UNKNOWN), + String.valueOf(Global.Wearable.TETHERED_CONFIG_STANDALONE), + String.valueOf(Global.Wearable.TETHERED_CONFIG_TETHERED) + })); + VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index b0a19270018a..284b06b86cb6 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 = 217; + private static final int SETTINGS_VERSION = 218; private final int mUserId; @@ -5334,74 +5334,73 @@ public class SettingsProvider extends ContentProvider { if (currentVersion == 203) { // Version 203: initialize entries migrated from wear settings provide. - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.HAS_PAY_TOKENS, false); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, 6); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.HOTWORD_DETECTION_ENABLED, getContext() .getResources() .getBoolean(R.bool.def_wearable_hotwordDetectionEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SMART_REPLIES_ENABLED, true); Setting locationMode = getSecureSettingsLocked(userId).getSettingLocked(Secure.LOCATION_MODE); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.OBTAIN_PAIRED_DEVICE_LOCATION, !locationMode.isNull() && !Integer.toString(Secure.LOCATION_MODE_OFF) .equals(locationMode.getValue())); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.PHONE_PLAY_STORE_AVAILABILITY, Global.Wearable.PHONE_PLAY_STORE_AVAILABILITY_UNKNOWN); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.BUG_REPORT, "user".equals(Build.TYPE) // is user build? ? Global.Wearable.BUG_REPORT_DISABLED : Global.Wearable.BUG_REPORT_ENABLED); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SMART_ILLUMINATE_ENABLED, getContext() .getResources() .getBoolean(R.bool.def_wearable_smartIlluminateEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.CLOCKWORK_AUTO_TIME, Global.Wearable.SYNC_TIME_FROM_PHONE); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE, Global.Wearable.SYNC_TIME_ZONE_FROM_PHONE); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.CLOCKWORK_24HR_TIME, false); - initGlobalSettingsDefaultValForWearLocked(Global.Wearable.AUTO_WIFI, true); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked(Global.Wearable.AUTO_WIFI, true); + initGlobalSettingsDefaultValLocked( Global.Wearable.WIFI_POWER_SAVE, getContext() .getResources() .getInteger( R.integer .def_wearable_offChargerWifiUsageLimitMinutes)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, 0L); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SETUP_SKIPPED, Global.Wearable.SETUP_SKIPPED_UNKNOWN); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.LAST_CALL_FORWARD_ACTION, Global.Wearable.CALL_FORWARD_NO_LAST_ACTION); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED, getContext() .getResources() .getBoolean(R.bool.def_wearable_muteWhenOffBodyEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.WEAR_OS_VERSION_STRING, ""); - initGlobalSettingsDefaultValForWearLocked(Global.Wearable.BUTTON_SET, false); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SIDE_BUTTON, getContext() .getResources() .getBoolean(R.bool.def_wearable_sideButtonPresent)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.ANDROID_WEAR_VERSION, Long.parseLong( getContext() @@ -5410,55 +5409,55 @@ public class SettingsProvider extends ContentProvider { final int editionGlobal = 1; final int editionLocal = 2; boolean isLe = getContext().getPackageManager().hasSystemFeature("cn.google"); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SYSTEM_EDITION, isLe ? editionLocal : editionGlobal); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.SYSTEM_CAPABILITIES, getWearSystemCapabilities(isLe)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.WEAR_PLATFORM_MR_NUMBER, SystemProperties.getInt("ro.cw_build.platform_mr", 0)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.MOBILE_SIGNAL_DETECTOR, getContext() .getResources() .getBoolean(R.bool.def_wearable_mobileSignalDetectorAllowed)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.AMBIENT_ENABLED, getContext() .getResources() .getBoolean(R.bool.def_wearable_ambientEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.AMBIENT_TILT_TO_WAKE, getContext() .getResources() .getBoolean(R.bool.def_wearable_tiltToWakeEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.AMBIENT_LOW_BIT_ENABLED_DEV, false); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.AMBIENT_TOUCH_TO_WAKE, getContext() .getResources() .getBoolean(R.bool.def_wearable_touchToWakeEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.AMBIENT_TILT_TO_BRIGHT, getContext() .getResources() .getBoolean(R.bool.def_wearable_tiltToBrightEnabled)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Global.Wearable.DECOMPOSABLE_WATCHFACE, false); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.AMBIENT_FORCE_WHEN_DOCKED, SystemProperties.getBoolean("ro.ambient.force_when_docked", false)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED, SystemProperties.getBoolean("ro.ambient.low_bit_enabled", false)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN, SystemProperties.getInt("ro.ambient.plugged_timeout_min", -1)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE, Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.USER_HFP_CLIENT_SETTING, Settings.Global.Wearable.HFP_CLIENT_UNSET); Setting disabledProfileSetting = @@ -5468,7 +5467,7 @@ public class SettingsProvider extends ContentProvider { disabledProfileSetting.isNull() ? 0 : Long.parseLong(disabledProfileSetting.getValue()); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.COMPANION_OS_VERSION, Settings.Global.Wearable.COMPANION_OS_VERSION_UNDEFINED); final boolean defaultBurnInProtectionEnabled = @@ -5482,17 +5481,17 @@ public class SettingsProvider extends ContentProvider { .config_enableBurnInProtection); final boolean forceBurnInProtection = SystemProperties.getBoolean("persist.debug.force_burn_in", false); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED, defaultBurnInProtectionEnabled || forceBurnInProtection); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE, getContext() .getResources() .getString( com.android.internal.R.string.config_wearSysUiPackage)); - initGlobalSettingsDefaultValForWearLocked( + initGlobalSettingsDefaultValLocked( Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY, getContext() .getResources() @@ -5622,63 +5621,16 @@ public class SettingsProvider extends ContentProvider { currentVersion = 210; } if (currentVersion == 210) { - final SettingsState secureSettings = getSecureSettingsLocked(userId); - final Setting currentSetting = secureSettings.getSettingLocked( - Secure.STATUS_BAR_SHOW_VIBRATE_ICON); - if (currentSetting.isNull()) { - final int defaultValueVibrateIconEnabled = getContext().getResources() - .getInteger(R.integer.def_statusBarVibrateIconEnabled); - secureSettings.insertSettingOverrideableByRestoreLocked( - Secure.STATUS_BAR_SHOW_VIBRATE_ICON, - String.valueOf(defaultValueVibrateIconEnabled), - null /* tag */, true /* makeDefault */, - SettingsState.SYSTEM_PACKAGE_NAME); - } + // Unused. Moved to version 217. currentVersion = 211; } if (currentVersion == 211) { - // Version 211: Set default value for - // Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS - final SettingsState secureSettings = getSecureSettingsLocked(userId); - final Setting lockScreenUnseenSetting = secureSettings - .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS); - if (lockScreenUnseenSetting.isNull()) { - final boolean defSetting = getContext().getResources() - .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications); - secureSettings.insertSettingOverrideableByRestoreLocked( - Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, - defSetting ? "1" : "0", - null /* tag */, - true /* makeDefault */, - SettingsState.SYSTEM_PACKAGE_NAME); - } - + // Unused. Moved to version 217. currentVersion = 212; } if (currentVersion == 212) { - final SettingsState globalSettings = getGlobalSettingsLocked(); - final SettingsState secureSettings = getSecureSettingsLocked(userId); - - final Setting bugReportInPowerMenu = globalSettings.getSettingLocked( - Global.BUGREPORT_IN_POWER_MENU); - - if (!bugReportInPowerMenu.isNull()) { - Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to " - + bugReportInPowerMenu.getValue() + " in Secure settings."); - secureSettings.insertSettingLocked( - Secure.BUGREPORT_IN_POWER_MENU, - bugReportInPowerMenu.getValue(), null /* tag */, - false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); - - // set global bug_report_in_power_menu setting to null since it's deprecated - Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null" - + " in Global settings since it's deprecated."); - globalSettings.insertSettingLocked( - Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */, - true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); - } - + // Unused. Moved to version 217. currentVersion = 213; } @@ -5804,6 +5756,89 @@ public class SettingsProvider extends ContentProvider { currentVersion = 217; } + if (currentVersion == 217) { + // Version 217: merge and rebase wear settings init logic. + + final SettingsState secureSettings = getSecureSettingsLocked(userId); + final SettingsState globalSettings = getGlobalSettingsLocked(); + + // Following init logic is moved from version 210 to this version in order to + // resolve version conflict with wear branch. + final Setting currentSetting = secureSettings.getSettingLocked( + Secure.STATUS_BAR_SHOW_VIBRATE_ICON); + if (currentSetting.isNull()) { + final int defaultValueVibrateIconEnabled = getContext().getResources() + .getInteger(R.integer.def_statusBarVibrateIconEnabled); + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.STATUS_BAR_SHOW_VIBRATE_ICON, + String.valueOf(defaultValueVibrateIconEnabled), + null /* tag */, true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + + // Set default value for Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS + // Following init logic is moved from version 211 to this version in order to + // resolve version conflict with wear branch. + final Setting lockScreenUnseenSetting = secureSettings + .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS); + if (lockScreenUnseenSetting.isNull()) { + final boolean defSetting = getContext().getResources() + .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications); + secureSettings.insertSettingOverrideableByRestoreLocked( + Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, + defSetting ? "1" : "0", + null /* tag */, + true /* makeDefault */, + SettingsState.SYSTEM_PACKAGE_NAME); + } + + // Following init logic is moved from version 212 to this version in order to + // resolve version conflict with wear branch. + final Setting bugReportInPowerMenu = globalSettings.getSettingLocked( + Global.BUGREPORT_IN_POWER_MENU); + + if (!bugReportInPowerMenu.isNull()) { + Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to " + + bugReportInPowerMenu.getValue() + " in Secure settings."); + secureSettings.insertSettingLocked( + Secure.BUGREPORT_IN_POWER_MENU, + bugReportInPowerMenu.getValue(), null /* tag */, + false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); + + // set global bug_report_in_power_menu setting to null since it's deprecated + Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null" + + " in Global settings since it's deprecated."); + globalSettings.insertSettingLocked( + Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */, + true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME); + } + + // Following init logic is rebased from wear OS branch. + // Initialize default value of tether configuration to unknown. + initGlobalSettingsDefaultValLocked( + Settings.Global.Wearable.TETHER_CONFIG_STATE, + Global.Wearable.TETHERED_CONFIG_UNKNOWN); + // Init paired device location setting from resources. + initGlobalSettingsDefaultValLocked( + Global.Wearable.OBTAIN_PAIRED_DEVICE_LOCATION, + getContext() + .getResources() + .getInteger(R.integer.def_paired_device_location_mode)); + // Init media packages from resources. + final String mediaControlsPackage = getContext().getResources().getString( + com.android.internal.R.string.config_wearMediaControlsPackage); + final String mediaSessionsPackage = getContext().getResources().getString( + com.android.internal.R.string.config_wearMediaSessionsPackage); + initGlobalSettingsDefaultValLocked( + Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, + mediaControlsPackage); + initGlobalSettingsDefaultValLocked( + Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE, + mediaSessionsPackage); + + currentVersion = 218; + } + // vXXX: Add new settings above this point. if (currentVersion != newVersion) { @@ -5821,19 +5856,19 @@ public class SettingsProvider extends ContentProvider { return currentVersion; } - private void initGlobalSettingsDefaultValForWearLocked(String key, boolean val) { - initGlobalSettingsDefaultValForWearLocked(key, val ? "1" : "0"); + private void initGlobalSettingsDefaultValLocked(String key, boolean val) { + initGlobalSettingsDefaultValLocked(key, val ? "1" : "0"); } - private void initGlobalSettingsDefaultValForWearLocked(String key, int val) { - initGlobalSettingsDefaultValForWearLocked(key, String.valueOf(val)); + private void initGlobalSettingsDefaultValLocked(String key, int val) { + initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } - private void initGlobalSettingsDefaultValForWearLocked(String key, long val) { - initGlobalSettingsDefaultValForWearLocked(key, String.valueOf(val)); + private void initGlobalSettingsDefaultValLocked(String key, long val) { + initGlobalSettingsDefaultValLocked(key, String.valueOf(val)); } - private void initGlobalSettingsDefaultValForWearLocked(String key, String val) { + private void initGlobalSettingsDefaultValLocked(String key, String val) { final SettingsState globalSettings = getGlobalSettingsLocked(); Setting currentSetting = globalSettings.getSettingLocked(key); if (currentSetting.isNull()) { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 19f1a86ec90c..a202e1614b67 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -627,7 +627,6 @@ public class SettingsBackupTest { Settings.Global.Wearable.STEM_3_DATA, Settings.Global.Wearable.STEM_3_DEFAULT_DATA, Settings.Global.Wearable.WEAR_OS_VERSION_STRING, - Settings.Global.Wearable.BUTTON_SET, Settings.Global.Wearable.SIDE_BUTTON, Settings.Global.Wearable.ANDROID_WEAR_VERSION, Settings.Global.Wearable.SYSTEM_CAPABILITIES, @@ -643,6 +642,7 @@ public class SettingsBackupTest { Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE, Settings.Global.Wearable.COMPANION_BLE_ROLE, Settings.Global.Wearable.COMPANION_NAME, + Settings.Global.Wearable.COMPANION_APP_NAME, Settings.Global.Wearable.USER_HFP_CLIENT_SETTING, Settings.Global.Wearable.COMPANION_OS_VERSION, Settings.Global.Wearable.ENABLE_ALL_LANGUAGES, @@ -662,13 +662,21 @@ public class SettingsBackupTest { Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED, Settings.Global.Wearable.BEDTIME_MODE, Settings.Global.Wearable.BEDTIME_HARD_MODE, - Settings.Global.Wearable.EARLY_UPDATES_STATUS, Settings.Global.Wearable.RSB_WAKE_ENABLED, Settings.Global.Wearable.LOCK_SCREEN_STATE, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE, Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED, - Settings.Global.Wearable.SCREENSHOT_ENABLED); + Settings.Global.Wearable.SCREENSHOT_ENABLED, + Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, + Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN, + Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND, + Settings.Global.Wearable.CUSTOM_COLOR_BACKGROUND, + Settings.Global.Wearable.PHONE_SWITCHING_STATUS, + Settings.Global.Wearable.TETHER_CONFIG_STATE, + Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED, + Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE, + Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE); private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS = newHashSet( diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 3007d4a79d13..7a1d9a3ad025 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -156,9 +156,10 @@ android_library { "WifiTrackerLib", "WindowManager-Shell", "SystemUIAnimationLib", + "SystemUICommon", + "SystemUICustomizationLib", "SystemUIPluginLib", "SystemUISharedLib", - "SystemUICustomizationLib", "SystemUI-statsd", "SettingsLib", "androidx.core_core-ktx", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 36a0b5dd72b8..dabb5780621c 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -347,15 +347,6 @@ <uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" /> - <!-- Intent Chooser --> - <permission - android:name="android.permission.ADD_CHOOSER_PINS" - android:protectionLevel="signature" /> - <uses-permission android:name="android.permission.ADD_CHOOSER_PINS" /> - <permission - android:name="android.permission.RECEIVE_CHOOSER_PIN_MIGRATION" - android:protectionLevel="signature" /> - <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" /> <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" /> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml new file mode 100644 index 000000000000..1d67066028be --- /dev/null +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/action_bar_title" + style="@style/TextAppearance.AppCompat.Title" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:maxLines="5"/> +</LinearLayout> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java index 02d279fa4962..5ed450abede5 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.accessibilitymenu.activity; +import android.app.ActionBar; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -24,6 +25,7 @@ import android.net.Uri; import android.os.Bundle; import android.provider.Browser; import android.provider.Settings; +import android.widget.TextView; import android.view.View; import androidx.annotation.Nullable; @@ -46,6 +48,13 @@ public class A11yMenuSettingsActivity extends FragmentActivity { .beginTransaction() .replace(android.R.id.content, new A11yMenuPreferenceFragment()) .commit(); + + ActionBar actionBar = getActionBar(); + actionBar.setDisplayShowCustomEnabled(true); + actionBar.setCustomView(R.layout.preferences_action_bar); + ((TextView) findViewById(R.id.action_bar_title)).setText( + getResources().getString(R.string.accessibility_menu_settings_name) + ); } /** diff --git a/packages/SystemUI/common/.gitignore b/packages/SystemUI/common/.gitignore new file mode 100644 index 000000000000..f9a33dbbcc7e --- /dev/null +++ b/packages/SystemUI/common/.gitignore @@ -0,0 +1,9 @@ +.idea/ +.gradle/ +gradle/ +build/ +gradlew* +local.properties +*.iml +android.properties +buildSrc
\ No newline at end of file diff --git a/packages/SystemUI/common/Android.bp b/packages/SystemUI/common/Android.bp new file mode 100644 index 000000000000..e36ada89b207 --- /dev/null +++ b/packages/SystemUI/common/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"], +} + +android_library { + + name: "SystemUICommon", + + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + + static_libs: [ + "androidx.core_core-ktx", + ], + + manifest: "AndroidManifest.xml", + kotlincflags: ["-Xjvm-default=all"], +} diff --git a/packages/SystemUI/common/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml new file mode 100644 index 000000000000..6f757eb67d2e --- /dev/null +++ b/packages/SystemUI/common/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.systemui.common"> + +</manifest> diff --git a/packages/SystemUI/common/OWNERS b/packages/SystemUI/common/OWNERS new file mode 100644 index 000000000000..9b8a79e6f3c7 --- /dev/null +++ b/packages/SystemUI/common/OWNERS @@ -0,0 +1,2 @@ +darrellshi@google.com +evanlaird@google.com diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md new file mode 100644 index 000000000000..1cc5277aa83e --- /dev/null +++ b/packages/SystemUI/common/README.md @@ -0,0 +1,5 @@ +# SystemUICommon + +`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies. + +To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate. diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt index 4773f54a079e..de49d1c2c5ee 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt +++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.plugins.util +package com.android.systemui.common.buffer import kotlin.math.max diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp index fb1c454de70d..e306d4aac398 100644 --- a/packages/SystemUI/plugin/Android.bp +++ b/packages/SystemUI/plugin/Android.bp @@ -37,6 +37,7 @@ java_library { "error_prone_annotations", "PluginCoreLib", "SystemUIAnimationLib", + "SystemUICommon", ], } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt index 52dfc55c105d..f71c137363c5 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.annotation.VisibleForTesting class WeatherData -private constructor( +constructor( val description: String, val state: WeatherStateIcon, val useCelsius: Boolean, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 3e34885a6d9c..4a6e0b61ecc9 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -18,7 +18,7 @@ package com.android.systemui.plugins.log import android.os.Trace import android.util.Log -import com.android.systemui.plugins.util.RingBuffer +import com.android.systemui.common.buffer.RingBuffer import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.concurrent.ArrayBlockingQueue diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml new file mode 100644 index 000000000000..61a1cb5f1d31 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<animated-vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25"> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/> + <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/> + <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/> + <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_3_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/ic_palette.xml b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml new file mode 100644 index 000000000000..cbea369c0236 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,395 112,322Q144,249 199.5,195Q255,141 329.5,110.5Q404,80 489,80Q568,80 639,106.5Q710,133 763.5,180Q817,227 848.5,291.5Q880,356 880,433Q880,541 817,603.5Q754,666 650,666L575,666Q557,666 544,680Q531,694 531,711Q531,738 545.5,757Q560,776 560,801Q560,839 539,859.5Q518,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM247,506Q267,506 282,491Q297,476 297,456Q297,436 282,421Q267,406 247,406Q227,406 212,421Q197,436 197,456Q197,476 212,491Q227,506 247,506ZM373,336Q393,336 408,321Q423,306 423,286Q423,266 408,251Q393,236 373,236Q353,236 338,251Q323,266 323,286Q323,306 338,321Q353,336 373,336ZM587,336Q607,336 622,321Q637,306 637,286Q637,266 622,251Q607,236 587,236Q567,236 552,251Q537,266 537,286Q537,306 552,321Q567,336 587,336ZM718,506Q738,506 753,491Q768,476 768,456Q768,436 753,421Q738,406 718,406Q698,406 683,421Q668,436 668,456Q668,476 683,491Q698,506 718,506ZM480,820Q491,820 495.5,815.5Q500,811 500,801Q500,787 485.5,775Q471,763 471,722Q471,676 501,641Q531,606 577,606L650,606Q726,606 773,561.5Q820,517 820,433Q820,301 720,220.5Q620,140 489,140Q343,140 241.5,238.5Q140,337 140,480Q140,621 239.5,720.5Q339,820 480,820Z"/> +</vector> diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index 951d6fed0a17..f3325ecefada 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -104,4 +104,9 @@ android:fromId="@id/unlocked" android:toId="@id/locked" android:drawable="@drawable/unlocked_to_locked" /> + + <transition + android:fromId="@id/locked_fp" + android:toId="@id/locked" + android:drawable="@drawable/fp_to_locked" /> </animated-selector> diff --git a/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml new file mode 100644 index 000000000000..8c2937c8c483 --- /dev/null +++ b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<pathInterpolator + xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.30" + android:controlY1="0.00" + android:controlX2="0.33" + android:controlY2="1.00" /> diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml new file mode 100644 index 000000000000..5fa88224968b --- /dev/null +++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<set + xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <scale + android:interpolator="@android:anim/accelerate_decelerate_interpolator" + android:duration="200" + android:fromXScale="0.5" + android:fromYScale="0.5" + android:toXScale="1.02" + android:toYScale="1.02" + android:pivotX="50%" + android:pivotY="50%" /> + + <scale + android:interpolator="@anim/keyguard_settings_popup_ease_out_interpolator" + android:startOffset="200" + android:duration="200" + android:fromXScale="1" + android:fromYScale="1" + android:toXScale="0.98" + android:toYScale="0.98" + android:pivotX="50%" + android:pivotY="50%" /> + + <alpha + android:interpolator="@android:anim/linear_interpolator" + android:duration="83" + android:fromAlpha="0" + android:toAlpha="1" /> + +</set> diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml new file mode 100644 index 000000000000..a6938defd4be --- /dev/null +++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<set + xmlns:android="http://schemas.android.com/apk/res/android" + android:shareInterpolator="false"> + + <scale + android:interpolator="@android:anim/accelerate_interpolator" + android:duration="233" + android:fromXScale="1" + android:fromYScale="1" + android:toXScale="0.5" + android:toYScale="0.5" + android:pivotX="50%" + android:pivotY="50%" /> + + <alpha + android:interpolator="@android:anim/linear_interpolator" + android:delay="150" + android:duration="83" + android:fromAlpha="1" + android:toAlpha="0" /> + +</set> diff --git a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml b/packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml index da581061370b..da581061370b 100644 --- a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml +++ b/packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml diff --git a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml b/packages/SystemUI/res/drawable/ic_shade_sim_card.xml index 6eda929b54d3..6eda929b54d3 100644 --- a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml +++ b/packages/SystemUI/res/drawable/ic_shade_sim_card.xml diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml index 3807b92ae39d..a0ceb81d42f4 100644 --- a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml +++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml @@ -17,17 +17,17 @@ <ripple xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:color="?android:attr/colorControlHighlight"> + android:color="#4d000000"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> - <corners android:radius="28dp" /> + <corners android:radius="@dimen/keyguard_affordance_fixed_radius" /> </shape> </item> <item> <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface" /> - <corners android:radius="28dp" /> + <solid android:color="?androidprv:attr/materialColorOnBackground" /> + <corners android:radius="@dimen/keyguard_affordance_fixed_radius" /> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml index dffe40b8454a..441f963a855a 100644 --- a/packages/SystemUI/res/layout/combined_qs_header.xml +++ b/packages/SystemUI/res/layout/combined_qs_header.xml @@ -94,7 +94,7 @@ <include android:id="@+id/carrier_group" - layout="@layout/qs_carrier_group" + layout="@layout/shade_carrier_group" app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height" android:minHeight="@dimen/large_screen_shade_header_min_height" app:layout_constraintWidth_min="48dp" diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 4048a39344bd..e9acf3fa10c8 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -20,7 +20,7 @@ android:id="@+id/keyguard_bottom_area" android:layout_height="match_parent" android:layout_width="match_parent" - android:outlineProvider="none" > <!-- Put it above the status bar header --> + android:outlineProvider="none" > <LinearLayout android:id="@+id/keyguard_indication_area" @@ -59,33 +59,55 @@ </LinearLayout> - <com.android.systemui.animation.view.LaunchableImageView - android:id="@+id/start_button" - android:layout_height="@dimen/keyguard_affordance_fixed_height" - android:layout_width="@dimen/keyguard_affordance_fixed_width" - android:layout_gravity="bottom|start" - android:scaleType="fitCenter" - android:padding="@dimen/keyguard_affordance_fixed_padding" - android:tint="?android:attr/textColorPrimary" - android:background="@drawable/keyguard_bottom_affordance_bg" - android:foreground="@drawable/keyguard_bottom_affordance_selected_border" - android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset" + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_gravity="bottom" + android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset" android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" - android:visibility="gone" /> + android:gravity="bottom" + > - <com.android.systemui.animation.view.LaunchableImageView - android:id="@+id/end_button" - android:layout_height="@dimen/keyguard_affordance_fixed_height" - android:layout_width="@dimen/keyguard_affordance_fixed_width" - android:layout_gravity="bottom|end" - android:scaleType="fitCenter" - android:padding="@dimen/keyguard_affordance_fixed_padding" - android:tint="?android:attr/textColorPrimary" - android:background="@drawable/keyguard_bottom_affordance_bg" - android:foreground="@drawable/keyguard_bottom_affordance_selected_border" - android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset" - android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" - android:visibility="gone" /> + <com.android.systemui.animation.view.LaunchableImageView + android:id="@+id/start_button" + android:layout_height="@dimen/keyguard_affordance_fixed_height" + android:layout_width="@dimen/keyguard_affordance_fixed_width" + android:scaleType="fitCenter" + android:padding="@dimen/keyguard_affordance_fixed_padding" + android:tint="?android:attr/textColorPrimary" + android:background="@drawable/keyguard_bottom_affordance_bg" + android:foreground="@drawable/keyguard_bottom_affordance_selected_border" + android:visibility="invisible" /> + + <FrameLayout + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="wrap_content" + android:paddingHorizontal="24dp" + > + <include + android:id="@+id/keyguard_settings_button" + layout="@layout/keyguard_settings_popup_menu" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="gone" + /> + </FrameLayout> + + <com.android.systemui.animation.view.LaunchableImageView + android:id="@+id/end_button" + android:layout_height="@dimen/keyguard_affordance_fixed_height" + android:layout_width="@dimen/keyguard_affordance_fixed_width" + android:scaleType="fitCenter" + android:padding="@dimen/keyguard_affordance_fixed_padding" + android:tint="?android:attr/textColorPrimary" + android:background="@drawable/keyguard_bottom_affordance_bg" + android:foreground="@drawable/keyguard_bottom_affordance_selected_border" + android:visibility="invisible" /> + + </LinearLayout> <FrameLayout android:id="@+id/overlay_container" diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml index 89d88fea1817..65ee8b370f9b 100644 --- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml +++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml @@ -15,25 +15,24 @@ ~ --> -<LinearLayout +<com.android.systemui.animation.view.LaunchableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minHeight="52dp" + android:minHeight="@dimen/keyguard_affordance_fixed_height" android:orientation="horizontal" android:gravity="center_vertical" android:background="@drawable/keyguard_settings_popup_menu_background" - android:paddingStart="16dp" - android:paddingEnd="24dp" - android:paddingVertical="16dp"> + android:padding="12dp"> <ImageView android:id="@+id/icon" - android:layout_width="20dp" - android:layout_height="20dp" - android:layout_marginEnd="16dp" - android:tint="?android:attr/textColorPrimary" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_marginEnd="8dp" + android:tint="?androidprv:attr/materialColorOnSecondaryFixed" android:importantForAccessibility="no" tools:ignore="UseAppTint" /> @@ -42,9 +41,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSecondaryFixed" android:textSize="14sp" android:maxLines="1" android:ellipsize="end" /> -</LinearLayout>
\ No newline at end of file +</com.android.systemui.animation.view.LaunchableLinearLayout> diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/shade_carrier.xml index a854660f64f8..0fed393a7ed3 100644 --- a/packages/SystemUI/res/layout/qs_carrier.xml +++ b/packages/SystemUI/res/layout/shade_carrier.xml @@ -14,7 +14,7 @@ ~ limitations under the License --> -<com.android.systemui.qs.carrier.QSCarrier +<com.android.systemui.shade.carrier.ShadeCarrier xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/linear_carrier" android:layout_width="wrap_content" @@ -29,7 +29,7 @@ android:focusable="true" > <com.android.systemui.util.AutoMarqueeTextView - android:id="@+id/qs_carrier_text" + android:id="@+id/shade_carrier_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" @@ -53,4 +53,4 @@ android:layout_marginStart="@dimen/qs_carrier_margin_width" android:visibility="gone" /> -</com.android.systemui.qs.carrier.QSCarrier>
\ No newline at end of file +</com.android.systemui.shade.carrier.ShadeCarrier>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/shade_carrier_group.xml index 6e13ab972226..2e8f98cbd190 100644 --- a/packages/SystemUI/res/layout/qs_carrier_group.xml +++ b/packages/SystemUI/res/layout/shade_carrier_group.xml @@ -15,7 +15,7 @@ --> <!-- Extends LinearLayout --> -<com.android.systemui.qs.carrier.QSCarrierGroup +<com.android.systemui.shade.carrier.ShadeCarrierGroup xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/qs_mobile" android:layout_width="0dp" @@ -39,25 +39,25 @@ android:visibility="gone"/> <include - layout="@layout/qs_carrier" + layout="@layout/shade_carrier" android:id="@+id/carrier1" android:layout_weight="1"/> <View - android:id="@+id/qs_carrier_divider1" + android:id="@+id/shade_carrier_divider1" android:layout_width="@dimen/qs_header_carrier_separator_width" android:layout_height="match_parent" android:visibility="gone" android:importantForAccessibility="no"/> <include - layout="@layout/qs_carrier" + layout="@layout/shade_carrier" android:id="@+id/carrier2" android:layout_weight="1" android:visibility="gone"/> <View - android:id="@+id/qs_carrier_divider2" + android:id="@+id/shade_carrier_divider2" android:layout_width="@dimen/qs_header_carrier_separator_width" android:layout_height="match_parent" android:layout_weight="1" @@ -65,9 +65,9 @@ android:importantForAccessibility="no"/> <include - layout="@layout/qs_carrier" + layout="@layout/shade_carrier" android:id="@+id/carrier3" android:layout_weight="1" android:visibility="gone"/> -</com.android.systemui.qs.carrier.QSCarrierGroup>
\ No newline at end of file +</com.android.systemui.shade.carrier.ShadeCarrierGroup>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index a11ffcda9830..d7106762d17b 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -120,10 +120,6 @@ /> </com.android.systemui.shade.NotificationsQuickSettingsContainer> - <include - layout="@layout/keyguard_bottom_area" - android:visibility="gone" /> - <ViewStub android:id="@+id/keyguard_user_switcher_stub" android:layout="@layout/keyguard_user_switcher" @@ -153,9 +149,7 @@ </com.android.keyguard.LockIconView> - <FrameLayout - android:id="@+id/preview_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> + <include + layout="@layout/keyguard_bottom_area" + android:visibility="gone" /> </com.android.systemui.shade.NotificationPanelView> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 714d495af762..4db42aacbdcd 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1774,13 +1774,6 @@ <dimen name="rear_display_title_top_padding">24dp</dimen> <dimen name="rear_display_title_bottom_padding">16dp</dimen> - <!-- - Vertical distance between the pointer and the popup menu that shows up on the lock screen when - it is long-pressed. - --> - <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen> - - <!-- Bouncer user switcher margins --> <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen> <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index befbfab7dbc3..675ae326b5a2 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -36,7 +36,7 @@ fade_out_complete_frame --> <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen> - <integer name="qs_carrier_max_em">7</integer> + <integer name="shade_carrier_max_em">7</integer> <!-- Maximum number of notification icons shown on the Always on Display (excluding overflow dot) --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f1777f84cd6d..74ae954a539c 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3057,13 +3057,17 @@ <string name="call_from_work_profile_close">Close</string> <!-- - Label for a menu item in a menu that is shown when the user wishes to configure the lock screen. + Label for a menu item in a menu that is shown when the user wishes to customize the lock screen. Clicking on this menu item takes the user to a screen where they can modify the settings of the lock screen. + It is critical that this text is as short as possible. If needed, translators should feel free + to drop "lock screen" from their translation and keep just "Customize" or "Customization", in + cases when that verb is not available in their target language. + [CHAR LIMIT=32] --> - <string name="lock_screen_settings">Lock screen settings</string> + <string name="lock_screen_settings">Customize lock screen</string> <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]--> <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 064cea112b5a..2098aea87ab2 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1399,4 +1399,9 @@ <style name="ShortcutItemBackground"> <item name="android:background">@color/ksh_key_item_new_background</item> </style> + + <style name="LongPressLockScreenAnimation"> + <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item> + <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item> + </style> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 4d7d0ea264a4..3c447a8eb6d8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -124,6 +124,8 @@ public class QuickStepContract { public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29; // The notification panel expansion fraction is > 0 public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30; + // When keyguard will be dismissed but didn't start animation yet + public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants @@ -172,6 +174,7 @@ public class QuickStepContract { SYSUI_STATE_AWAKE, SYSUI_STATE_WAKEFULNESS_TRANSITION, SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, + SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, }) public @interface SystemUiStateFlags {} @@ -270,6 +273,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) { str.add("notif_visible"); } + if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) { + str.add("keygrd_going_away"); + } return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java index 7971e84769a2..b15378570358 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -21,7 +21,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.net.wifi.WifiManager; +import android.os.Trace; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener; @@ -37,6 +37,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository; import com.android.systemui.telephony.TelephonyListenerManager; import java.util.List; @@ -50,7 +51,10 @@ import javax.inject.Inject; * Controller that generates text including the carrier names and/or the status of all the SIM * interfaces in the device. Through a callback, the updates can be retrieved either as a list or * separated by a given separator {@link CharSequence}. + * + * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead */ +@Deprecated public class CarrierTextManager { private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "CarrierTextController"; @@ -64,7 +68,7 @@ public class CarrierTextManager { private final AtomicBoolean mNetworkSupported = new AtomicBoolean(); @VisibleForTesting protected KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final WifiManager mWifiManager; + private final WifiRepository mWifiRepository; private final boolean[] mSimErrorState; private final int mSimSlotsNumber; @Nullable // Check for nullability before dispatching @@ -165,7 +169,7 @@ public class CarrierTextManager { CharSequence separator, boolean showAirplaneMode, boolean showMissingSim, - @Nullable WifiManager wifiManager, + WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @@ -177,8 +181,7 @@ public class CarrierTextManager { mShowAirplaneMode = showAirplaneMode; mShowMissingSim = showMissingSim; - - mWifiManager = wifiManager; + mWifiRepository = wifiRepository; mTelephonyManager = telephonyManager; mSeparator = separator; mTelephonyListenerManager = telephonyListenerManager; @@ -297,6 +300,7 @@ public class CarrierTextManager { } protected void updateCarrierText() { + Trace.beginSection("CarrierTextManager#updateCarrierText"); boolean allSimsMissing = true; boolean anySimReadyAndInService = false; CharSequence displayText = null; @@ -329,20 +333,20 @@ public class CarrierTextManager { carrierNames[i] = carrierTextForSimState; } if (simState == TelephonyManager.SIM_STATE_READY) { + Trace.beginSection("WFC check"); ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId); if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) { // hack for WFC (IWLAN) not turning off immediately once // Wi-Fi is disassociated or disabled if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN - || (mWifiManager != null && mWifiManager.isWifiEnabled() - && mWifiManager.getConnectionInfo() != null - && mWifiManager.getConnectionInfo().getBSSID() != null)) { + || mWifiRepository.isWifiConnectedWithValidSsid()) { if (DEBUG) { Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss); } anySimReadyAndInService = true; } } + Trace.endSection(); } } // Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY @@ -406,6 +410,7 @@ public class CarrierTextManager { subsIds, airplaneMode); postToCallback(info); + Trace.endSection(); } @VisibleForTesting @@ -633,7 +638,7 @@ public class CarrierTextManager { public static class Builder { private final Context mContext; private final String mSeparator; - private final WifiManager mWifiManager; + private final WifiRepository mWifiRepository; private final TelephonyManager mTelephonyManager; private final TelephonyListenerManager mTelephonyListenerManager; private final WakefulnessLifecycle mWakefulnessLifecycle; @@ -647,7 +652,7 @@ public class CarrierTextManager { public Builder( Context context, @Main Resources resources, - @Nullable WifiManager wifiManager, + @Nullable WifiRepository wifiRepository, TelephonyManager telephonyManager, TelephonyListenerManager telephonyListenerManager, WakefulnessLifecycle wakefulnessLifecycle, @@ -657,7 +662,7 @@ public class CarrierTextManager { mContext = context; mSeparator = resources.getString( com.android.internal.R.string.kg_text_message_separator); - mWifiManager = wifiManager; + mWifiRepository = wifiRepository; mTelephonyManager = telephonyManager; mTelephonyListenerManager = telephonyListenerManager; mWakefulnessLifecycle = wakefulnessLifecycle; @@ -681,7 +686,7 @@ public class CarrierTextManager { /** Create a CarrierTextManager. */ public CarrierTextManager build() { return new CarrierTextManager( - mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager, + mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt index 3a89c13ddd64..40f6f48288dc 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information. */ data class KeyguardActiveUnlockModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index c98e9b40e7ab..5b0e29005d82 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information associated. */ data class KeyguardFaceListenModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt index 57130ed80d26..b8c0ccbd8aaa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt @@ -17,9 +17,9 @@ package com.android.keyguard import android.annotation.CurrentTimeMillisLong +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer /** Verbose debug information. */ data class KeyguardFingerprintListenModel( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index c4df836e401f..6f549881d12a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -16,12 +16,37 @@ package com.android.keyguard; +import static androidx.constraintlayout.widget.ConstraintSet.END; +import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; + +import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; + +import android.animation.Animator; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.graphics.Rect; +import android.transition.ChangeBounds; +import android.transition.Transition; +import android.transition.TransitionListenerAdapter; +import android.transition.TransitionManager; +import android.transition.TransitionSet; +import android.transition.TransitionValues; import android.util.Slog; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.annotation.VisibleForTesting; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; + +import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.KeyguardClockSwitch.ClockSize; import com.android.keyguard.logging.KeyguardLogger; +import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ClockController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; @@ -42,6 +67,12 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "KeyguardStatusViewController"; + /** + * Duration to use for the animator when the keyguard status view alignment changes, and a + * custom clock animation is in use. + */ + private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; + private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); @@ -50,8 +81,25 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final ConfigurationController mConfigurationController; private final KeyguardVisibilityHelper mKeyguardVisibilityHelper; + private final FeatureFlags mFeatureFlags; + private final InteractionJankMonitor mInteractionJankMonitor; private final Rect mClipBounds = new Rect(); + private Boolean mStatusViewCentered = true; + + private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = + new TransitionListenerAdapter() { + @Override + public void onTransitionCancel(Transition transition) { + mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + } + + @Override + public void onTransitionEnd(Transition transition) { + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + } + }; + @Inject public KeyguardStatusViewController( KeyguardStatusView keyguardStatusView, @@ -62,7 +110,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV ConfigurationController configurationController, DozeParameters dozeParameters, ScreenOffAnimationController screenOffAnimationController, - KeyguardLogger logger) { + KeyguardLogger logger, + FeatureFlags featureFlags, + InteractionJankMonitor interactionJankMonitor) { super(keyguardStatusView); mKeyguardSliceViewController = keyguardSliceViewController; mKeyguardClockSwitchController = keyguardClockSwitchController; @@ -71,6 +121,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController, dozeParameters, screenOffAnimationController, /* animateYPos= */ true, logger.getBuffer()); + mInteractionJankMonitor = interactionJankMonitor; + mFeatureFlags = featureFlags; } @Override @@ -242,9 +294,141 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } } - /** Gets the current clock controller. */ - @Nullable - public ClockController getClockController() { - return mKeyguardClockSwitchController.getClock(); + /** + * Updates the alignment of the KeyguardStatusView and animates the transition if requested. + */ + public void updateAlignment( + ConstraintLayout notifContainerParent, + boolean splitShadeEnabled, + boolean shouldBeCentered, + boolean animate) { + if (mStatusViewCentered == shouldBeCentered) { + return; + } + + mStatusViewCentered = shouldBeCentered; + if (notifContainerParent == null) { + return; + } + + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(notifContainerParent); + int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline; + constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); + if (!animate) { + constraintSet.applyTo(notifContainerParent); + return; + } + + mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + ChangeBounds transition = new ChangeBounds(); + if (splitShadeEnabled) { + // Excluding media from the transition on split-shade, as it doesn't transition + // horizontally properly. + transition.excludeTarget(R.id.status_view_media_container, true); + } + + transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); + + ClockController clock = mKeyguardClockSwitchController.getClock(); + boolean customClockAnimation = clock != null + && clock.getConfig().getHasCustomPositionUpdatedAnimation(); + + if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { + // Find the clock, so we can exclude it from this transition. + FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large); + + // The clock container can sometimes be null. If it is, just fall back to the + // old animation rather than setting up the custom animations. + if (clockContainerView == null || clockContainerView.getChildCount() == 0) { + transition.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, transition); + } else { + View clockView = clockContainerView.getChildAt(0); + + transition.excludeTarget(clockView, /* exclude= */ true); + + TransitionSet set = new TransitionSet(); + set.addTransition(transition); + + SplitShadeTransitionAdapter adapter = + new SplitShadeTransitionAdapter(mKeyguardClockSwitchController); + + // Use linear here, so the actual clock can pick its own interpolator. + adapter.setInterpolator(Interpolators.LINEAR); + adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION); + adapter.addTarget(clockView); + set.addTransition(adapter); + set.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, set); + } + } else { + transition.addListener(mKeyguardStatusAlignmentTransitionListener); + TransitionManager.beginDelayedTransition(notifContainerParent, transition); + } + + constraintSet.applyTo(notifContainerParent); + } + + @VisibleForTesting + static class SplitShadeTransitionAdapter extends Transition { + private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; + private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; + + private final KeyguardClockSwitchController mController; + + @VisibleForTesting + SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) { + mController = controller; + } + + private void captureValues(TransitionValues transitionValues) { + Rect boundsRect = new Rect(); + boundsRect.left = transitionValues.view.getLeft(); + boundsRect.top = transitionValues.view.getTop(); + boundsRect.right = transitionValues.view.getRight(); + boundsRect.bottom = transitionValues.view.getBottom(); + transitionValues.values.put(PROP_BOUNDS, boundsRect); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Nullable + @Override + public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues, + @Nullable TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + + Rect from = (Rect) startValues.values.get(PROP_BOUNDS); + Rect to = (Rect) endValues.values.get(PROP_BOUNDS); + + anim.addUpdateListener(animation -> { + ClockController clock = mController.getClock(); + if (clock == null) { + return; + } + + clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction()); + }); + + return anim; + } + + @Override + public String[] getTransitionProperties() { + return TRANSITION_PROPERTIES; + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index e1bca89091b2..350c4ed084b9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -96,6 +96,7 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; import android.hardware.biometrics.SensorProperties; +import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorPropertiesInternal; @@ -1278,6 +1279,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) { lockedOutStateChanged = !mFaceLockedOutPermanent; mFaceLockedOutPermanent = true; + if (isFaceClass3()) { + updateFingerprintListeningState(BIOMETRIC_ACTION_STOP); + } } if (isHwUnavailable && cameraPrivacyEnabled) { @@ -1487,8 +1491,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent; // however the strong auth tracker does not include the temporary lockout // mFingerprintLockedOut. + // Class 3 biometric lockout will lockout ALL biometrics return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric) - && !mFingerprintLockedOut; + && (!isFingerprintClass3() || !isFingerprintLockedOut()) + && (!isFaceClass3() || !mFaceLockedOutPermanent); } /** @@ -1506,9 +1512,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @NonNull BiometricSourceType biometricSourceType) { switch (biometricSourceType) { case FINGERPRINT: - return isUnlockingWithBiometricAllowed(true); + return isUnlockingWithBiometricAllowed(isFingerprintClass3()); case FACE: - return isUnlockingWithBiometricAllowed(false); + return isUnlockingWithBiometricAllowed(isFaceClass3()); default: return false; } @@ -2473,7 +2479,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void updateFaceEnrolled(int userId) { - Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty() + final Boolean isFaceEnrolled = isFaceSupported() && mBiometricEnabledForUser.get(userId) && mAuthController.isFaceAuthEnrolled(userId); if (mIsFaceEnrolled != isFaceEnrolled) { @@ -2482,10 +2488,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mIsFaceEnrolled = isFaceEnrolled; } - public boolean isFaceSupported() { + private boolean isFaceSupported() { return mFaceManager != null && !mFaceSensorProperties.isEmpty(); } + private boolean isFingerprintSupported() { + return mFpm != null && !mFingerprintSensorProperties.isEmpty(); + } + /** * @return true if there's at least one udfps enrolled for the current user. */ @@ -2792,10 +2802,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || !mLockPatternUtils.isSecure(user); // Don't trigger active unlock if fp is locked out - final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent; + final boolean fpLockedOut = isFingerprintLockedOut(); // Don't trigger active unlock if primary auth is required - final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true); + final boolean primaryAuthRequired = !isUnlockingWithTrustAgentAllowed(); final boolean shouldTriggerActiveUnlock = (mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard) @@ -2857,7 +2867,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || mGoingToSleep || shouldListenForFingerprintAssistant || (mKeyguardOccluded && mIsDreaming) - || (mKeyguardOccluded && userDoesNotHaveTrust + || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing && (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing)); // Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an @@ -2949,7 +2959,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // allow face detection to happen even if stronger auth is required. When face is detected, // we show the bouncer. However, if the user manually locked down the device themselves, // never attempt to detect face. - final boolean supportsDetect = !mFaceSensorProperties.isEmpty() + final boolean supportsDetect = isFaceSupported() && mFaceSensorProperties.get(0).supportsFaceDetection && canBypass && !mPrimaryBouncerIsOrWillBeShowing && !isUserInLockdown(user); @@ -3104,7 +3114,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab : WAKE_REASON_UNKNOWN ).toFaceAuthenticateOptions(); // This would need to be updated for multi-sensor devices - final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty() + final boolean supportsFaceDetection = isFaceSupported() && mFaceSensorProperties.get(0).supportsFaceDetection; if (!isUnlockingWithBiometricAllowed(FACE)) { final boolean udfpsFingerprintAuthRunning = isUdfpsSupported() @@ -3166,21 +3176,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @return {@code true} if possible. */ public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) { - // This assumes that there is at most one face and at most one fingerprint sensor - return (mFaceManager != null && !mFaceSensorProperties.isEmpty() - && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG) - && isUnlockWithFacePossible(userId)) - || (mFpm != null && !mFingerprintSensorProperties.isEmpty() - && (mFingerprintSensorProperties.get(0).sensorStrength - != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId)); + return (!isFaceClass3() && isUnlockWithFacePossible(userId)) + || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId)); } @SuppressLint("MissingPermission") @VisibleForTesting boolean isUnlockWithFingerprintPossible(int userId) { // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. - boolean newFpEnrolled = mFpm != null - && !mFingerprintSensorProperties.isEmpty() + boolean newFpEnrolled = isFingerprintSupported() && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId); Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); if (oldFpEnrolled != newFpEnrolled) { @@ -3330,12 +3334,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // Immediately stop previous biometric listening states. // Resetting lockout states updates the biometric listening states. - if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { + if (isFaceSupported()) { stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING); handleFaceLockoutReset(mFaceManager.getLockoutModeForUser( mFaceSensorProperties.get(0).sensorId, userId)); } - if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) { + if (isFingerprintSupported()) { stopListeningForFingerprint(); handleFingerprintLockoutReset(mFpm.getLockoutModeForUser( mFingerprintSensorProperties.get(0).sensorId, userId)); @@ -4071,6 +4075,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return BIOMETRIC_LOCKOUT_RESET_DELAY_MS; } + @VisibleForTesting + protected boolean isFingerprintClass3() { + // This assumes that there is at most one fingerprint sensor property + return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0)); + } + + @VisibleForTesting + protected boolean isFaceClass3() { + // This assumes that there is at most one face sensor property + return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0)); + } + + private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) { + return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG; + } + /** * Unregister all listeners. */ @@ -4122,11 +4142,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab for (int subId : mServiceStates.keySet()) { pw.println(" " + subId + "=" + mServiceStates.get(subId)); } - if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) { + if (isFingerprintSupported()) { final int userId = mUserTracker.getUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId); pw.println(" Fingerprint state (user=" + userId + ")"); + pw.println(" isFingerprintClass3=" + isFingerprintClass3()); pw.println(" areAllFpAuthenticatorsRegistered=" + mAuthController.areAllFingerprintAuthenticatorsRegistered()); pw.println(" allowed=" @@ -4184,11 +4205,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mFingerprintListenBuffer.toList() ).printTableData(pw); } - if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { + if (isFaceSupported()) { final int userId = mUserTracker.getUserId(); final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId); BiometricAuthenticated face = mUserFaceAuthenticated.get(userId); pw.println(" Face authentication state (user=" + userId + ")"); + pw.println(" isFaceClass3=" + isFaceClass3()); pw.println(" allowed=" + (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric))); pw.println(" auth'd=" diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index ac0a3fd8dbc4..a678edc0eb06 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -28,7 +28,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.stack.AnimationProperties; -import com.android.systemui.statusbar.phone.AnimatorHandle; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +47,6 @@ public class KeyguardVisibilityHelper { private final ScreenOffAnimationController mScreenOffAnimationController; private boolean mAnimateYPos; private boolean mKeyguardViewVisibilityAnimating; - private AnimatorHandle mKeyguardAnimatorHandle; private boolean mLastOccludedState = false; private final AnimationProperties mAnimationProperties = new AnimationProperties(); private final LogBuffer mLogBuffer; @@ -85,10 +83,6 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - if (mKeyguardAnimatorHandle != null) { - mKeyguardAnimatorHandle.cancel(); - mKeyguardAnimatorHandle = null; - } mView.animate().cancel(); boolean isOccluded = mKeyguardStateController.isOccluded(); mKeyguardViewVisibilityAnimating = false; @@ -122,7 +116,7 @@ public class KeyguardVisibilityHelper { .setDuration(320) .setInterpolator(Interpolators.ALPHA_IN) .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); - log("keyguardFadingAway transition w/ Y Animation"); + log("keyguardFadingAway transition w/ Y Aniamtion"); } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { mKeyguardViewVisibilityAnimating = true; @@ -154,7 +148,7 @@ public class KeyguardVisibilityHelper { // Ask the screen off animation controller to animate the keyguard visibility for us // since it may need to be cancelled due to keyguard lifecycle events. - mKeyguardAnimatorHandle = mScreenOffAnimationController.animateInKeyguard( + mScreenOffAnimationController.animateInKeyguard( mView, mAnimateKeyguardStatusViewVisibleEndRunnable); } else { log("Direct set Visibility to VISIBLE"); diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 235a8bca6d1e..5f2afe8f755d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -294,6 +294,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { + if (wasShowingFpIcon) { + // fp icon was shown by UdfpsView, and now we still want to animate the transition + // in this drawable + mView.updateIcon(ICON_FINGERPRINT, false); + } mView.updateIcon(ICON_LOCK, false); mView.setContentDescription(mLockedLabel); mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt b/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt deleted file mode 100644 index 2f03259766c0..000000000000 --- a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui - -import android.content.ComponentName -import android.content.Context -import android.content.Context.MODE_PRIVATE -import android.content.Intent -import android.content.SharedPreferences -import android.os.Bundle -import android.os.Environment -import android.os.storage.StorageManager -import android.util.Log -import androidx.core.util.Supplier -import com.android.internal.R -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import java.io.File -import javax.inject.Inject - -/** - * Performs a migration of pinned targets to the unbundled chooser if legacy data exists. - * - * Sends an explicit broadcast with the contents of the legacy pin preferences. The broadcast is - * protected by the RECEIVE_CHOOSER_PIN_MIGRATION permission. This class requires the - * ADD_CHOOSER_PINS permission in order to be able to send this broadcast. - */ -class ChooserPinMigration -@Inject -constructor( - private val context: Context, - private val featureFlags: FeatureFlags, - private val broadcastSender: BroadcastSender, - legacyPinPrefsFileSupplier: LegacyPinPrefsFileSupplier, -) : CoreStartable { - - private val legacyPinPrefsFile = legacyPinPrefsFileSupplier.get() - private val chooserComponent = - ComponentName.unflattenFromString( - context.resources.getString(R.string.config_chooserActivity) - ) - - override fun start() { - if (migrationIsRequired()) { - doMigration() - } - } - - private fun migrationIsRequired(): Boolean { - return featureFlags.isEnabled(Flags.CHOOSER_MIGRATION_ENABLED) && - legacyPinPrefsFile.exists() && - chooserComponent?.packageName != null - } - - private fun doMigration() { - Log.i(TAG, "Beginning migration") - - val legacyPinPrefs = context.getSharedPreferences(legacyPinPrefsFile, MODE_PRIVATE) - - if (legacyPinPrefs.all.isEmpty()) { - Log.i(TAG, "No data to migrate, deleting legacy file") - } else { - sendSharedPreferences(legacyPinPrefs) - Log.i(TAG, "Legacy data sent, deleting legacy preferences") - - val legacyPinPrefsEditor = legacyPinPrefs.edit() - legacyPinPrefsEditor.clear() - if (!legacyPinPrefsEditor.commit()) { - Log.e(TAG, "Failed to delete legacy preferences") - return - } - } - - if (!legacyPinPrefsFile.delete()) { - Log.e(TAG, "Legacy preferences deleted, but failed to delete legacy preferences file") - return - } - - Log.i(TAG, "Legacy preference deletion complete") - } - - private fun sendSharedPreferences(sharedPreferences: SharedPreferences) { - val bundle = Bundle() - - sharedPreferences.all.entries.forEach { (key, value) -> - when (value) { - is Boolean -> bundle.putBoolean(key, value) - else -> Log.e(TAG, "Unsupported preference type for $key: ${value?.javaClass}") - } - } - - sendBundle(bundle) - } - - private fun sendBundle(bundle: Bundle) { - val intent = - Intent().apply { - `package` = chooserComponent?.packageName!! - action = BROADCAST_ACTION - putExtras(bundle) - } - broadcastSender.sendBroadcast(intent, BROADCAST_PERMISSION) - } - - companion object { - private const val TAG = "PinnedShareTargetMigration" - private const val BROADCAST_ACTION = "android.intent.action.CHOOSER_PIN_MIGRATION" - private const val BROADCAST_PERMISSION = "android.permission.RECEIVE_CHOOSER_PIN_MIGRATION" - - class LegacyPinPrefsFileSupplier @Inject constructor(private val context: Context) : - Supplier<File> { - - override fun get(): File { - val packageDirectory = - Environment.getDataUserCePackageDirectory( - StorageManager.UUID_PRIVATE_INTERNAL, - context.userId, - context.packageName, - ) - val sharedPrefsDirectory = File(packageDirectory, "shared_prefs") - return File(sharedPrefsDirectory, "chooser_pin_settings.xml") - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt index 179eb391af4e..a3e7d71a92f6 100644 --- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt @@ -35,6 +35,7 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils import com.android.systemui.animation.Interpolators +import com.android.systemui.biometrics.AuthController import com.android.systemui.log.ScreenDecorationsLogger import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.util.asIndenting @@ -52,6 +53,7 @@ class FaceScanningOverlay( val keyguardUpdateMonitor: KeyguardUpdateMonitor, val mainExecutor: Executor, val logger: ScreenDecorationsLogger, + val authController: AuthController, ) : ScreenDecorations.DisplayCutoutView(context, pos) { private var showScanningAnim = false private val rimPaint = Paint() @@ -102,7 +104,9 @@ class FaceScanningOverlay( } override fun enableShowProtection(show: Boolean) { - val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show + val animationRequired = + keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing + val showScanningAnimNow = animationRequired && show if (showScanningAnimNow == showScanningAnim) { return } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 12b5705190d9..f3c71da63594 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -210,8 +210,8 @@ public class SystemActions implements CoreStartable { // Saving in instance variable since to prevent GC since // NotificationShadeWindowController.registerCallback() only keeps weak references. mNotificationShadeCallback = - (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded, - isDreaming) -> + (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing, + panelExpanded, isDreaming) -> registerOrUnregisterDismissNotificationShadeAction(); mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 92344dbbfe15..09992290a9ad 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -1005,9 +1005,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. */ public boolean isRearFpsSupported() { - for (FingerprintSensorPropertiesInternal prop: mFpProps) { - if (prop.sensorType == TYPE_REAR) { - return true; + if (mFpProps != null) { + for (FingerprintSensorPropertiesInternal prop: mFpProps) { + if (prop.sensorType == TYPE_REAR) { + return true; + } } } return false; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index ac30311e1f96..aabdafb88558 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -563,6 +563,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { (TouchProcessorResult.ProcessedTouch) result; final NormalizedTouchData data = processedTouch.getTouchData(); + boolean shouldPilfer = false; mActivePointerId = processedTouch.getPointerOnSensorId(); switch (processedTouch.getEvent()) { case DOWN: @@ -581,8 +582,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mStatusBarStateController.isDozing()); // Pilfer if valid overlap, don't allow following events to reach keyguard - mInputManager.pilferPointers( - mOverlay.getOverlayView().getViewRootImpl().getInputToken()); + shouldPilfer = true; break; case UP: @@ -621,6 +621,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Always pilfer pointers that are within sensor area or when alternate bouncer is showing if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true) || mAlternateBouncerInteractor.isVisibleState()) { + shouldPilfer = true; + } + + // Execute the pilfer, never pilfer if a vertical swipe is in progress + if (shouldPilfer && mLockscreenShadeTransitionController.getQSDragProgress() == 0f + && !mPrimaryBouncerInteractor.isInTransit()) { mInputManager.pilferPointers( mOverlay.getOverlayView().getViewRootImpl().getInputToken()); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index f6b71336675f..691017b220f8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -324,10 +324,6 @@ public class BrightLineFalsingManager implements FalsingManager { @Override public boolean isFalseLongTap(@Penalty int penalty) { - if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) { - return false; - } - checkDestroyed(); if (skipFalsing(GENERIC)) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java index edda87527b1d..63b4288ce055 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java @@ -21,7 +21,6 @@ import static android.content.ClipDescription.CLASSIFICATION_COMPLETE; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN; -import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT; import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE; @@ -36,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.flags.FeatureFlags; import javax.inject.Inject; import javax.inject.Provider; @@ -59,7 +57,6 @@ public class ClipboardListener implements private final Provider<ClipboardOverlayController> mOverlayProvider; private final ClipboardToast mClipboardToast; private final ClipboardManager mClipboardManager; - private final FeatureFlags mFeatureFlags; private final UiEventLogger mUiEventLogger; private ClipboardOverlay mClipboardOverlay; @@ -68,13 +65,11 @@ public class ClipboardListener implements Provider<ClipboardOverlayController> clipboardOverlayControllerProvider, ClipboardToast clipboardToast, ClipboardManager clipboardManager, - FeatureFlags featureFlags, UiEventLogger uiEventLogger) { mContext = context; mOverlayProvider = clipboardOverlayControllerProvider; mClipboardToast = clipboardToast; mClipboardManager = clipboardManager; - mFeatureFlags = featureFlags; mUiEventLogger = uiEventLogger; } @@ -113,11 +108,7 @@ public class ClipboardListener implements } else { mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource); } - if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) { - mClipboardOverlay.setClipData(clipData, clipSource); - } else { - mClipboardOverlay.setClipDataLegacy(clipData, clipSource); - } + mClipboardOverlay.setClipData(clipData, clipSource); mClipboardOverlay.setOnSessionCompleteListener(() -> { // Session is complete, free memory until it's needed again. mClipboardOverlay = null; @@ -160,8 +151,6 @@ public class ClipboardListener implements } interface ClipboardOverlay { - void setClipDataLegacy(ClipData clipData, String clipSource); - void setClipData(ClipData clipData, String clipSource); void setOnSessionCompleteListener(Runnable runnable); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index e6affb06cacc..5230159aaef4 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -32,7 +32,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT; -import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT; import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; import android.animation.Animator; @@ -40,20 +39,14 @@ import android.animation.AnimatorListenerAdapter; import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ClipData; -import android.content.ClipDescription; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.graphics.Bitmap; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Looper; import android.provider.DeviceConfig; -import android.text.TextUtils; -import android.util.Log; -import android.util.Size; import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; @@ -72,7 +65,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; -import java.io.IOException; import java.util.Optional; import java.util.concurrent.Executor; @@ -170,9 +162,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Override public void onMinimizedViewTapped() { - if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) { - animateFromMinimized(); - } + animateFromMinimized(); } }; @@ -255,11 +245,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @VisibleForTesting void onInsetsChanged(WindowInsets insets, int orientation) { mView.setInsets(insets, orientation); - if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) { - if (shouldShowMinimized(insets) && !mIsMinimized) { - mIsMinimized = true; - mView.setMinimized(true); - } + if (shouldShowMinimized(insets) && !mIsMinimized) { + mIsMinimized = true; + mView.setMinimized(true); } } @@ -401,61 +389,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv }); } - @Override // ClipboardListener.ClipboardOverlay - public void setClipDataLegacy(ClipData clipData, String clipSource) { - if (mExitAnimator != null && mExitAnimator.isRunning()) { - mExitAnimator.cancel(); - } - reset(); - mClipboardLogger.setClipSource(clipSource); - String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied); - - boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null - && clipData.getDescription().getExtras() - .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); - boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) - && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource); - if (clipData == null || clipData.getItemCount() == 0) { - mView.showDefaultTextPreview(); - } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { - ClipData.Item item = clipData.getItemAt(0); - if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, - CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) { - if (item.getTextLinks() != null) { - classifyText(clipData.getItemAt(0), clipSource); - } - } - if (isSensitive) { - showEditableText(mContext.getString(R.string.clipboard_asterisks), true); - } else { - showEditableText(item.getText(), false); - } - mOnShareTapped = () -> shareContent(clipData); - mView.showShareChip(); - accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied); - } else if (clipData.getItemAt(0).getUri() != null) { - if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) { - accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied); - } - mOnShareTapped = () -> shareContent(clipData); - mView.showShareChip(); - } else { - mView.showDefaultTextPreview(); - } - if (!isRemote) { - maybeShowRemoteCopy(clipData); - } - animateIn(); - mView.announceForAccessibility(accessibilityAnnouncement); - if (isRemote) { - mTimeoutHandler.cancelTimeout(); - mOnUiUpdate = null; - } else { - mOnUiUpdate = mTimeoutHandler::resetTimeout; - mOnUiUpdate.run(); - } - } - private void maybeShowRemoteCopy(ClipData clipData) { Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext); // Only show remote copy if it's available. @@ -478,22 +411,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mOnSessionCompleteListener = runnable; } - private void classifyText(ClipData.Item item, String source) { - mBgExecutor.execute(() -> { - Optional<RemoteAction> action = mClipboardUtils.getAction(item, source); - mView.post(() -> { - mView.resetActionChips(); - action.ifPresent(remoteAction -> { - mView.setActionChip(remoteAction, () -> { - mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED); - animateOut(); - }); - mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN); - }); - }); - }); - } - private void monitorOutsideTouches() { InputManager inputManager = mContext.getSystemService(InputManager.class); mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0); @@ -534,43 +451,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv animateOut(); } - private void showEditableText(CharSequence text, boolean hidden) { - mView.showTextPreview(text.toString(), hidden); - mView.setEditAccessibilityAction(true); - mOnPreviewTapped = this::editText; - } - - private boolean tryShowEditableImage(Uri uri, boolean isSensitive) { - Runnable listener = () -> editImage(uri); - ContentResolver resolver = mContext.getContentResolver(); - String mimeType = resolver.getType(uri); - boolean isEditableImage = mimeType != null && mimeType.startsWith("image"); - if (isSensitive) { - mView.showImagePreview(null); - if (isEditableImage) { - mOnPreviewTapped = listener; - mView.setEditAccessibilityAction(true); - } - } else if (isEditableImage) { // if the MIMEtype is image, try to load - try { - int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale); - // The width of the view is capped, height maintains aspect ratio, so allow it to be - // taller if needed. - Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null); - mView.showImagePreview(thumbnail); - mView.setEditAccessibilityAction(true); - mOnPreviewTapped = listener; - } catch (IOException e) { - Log.e(TAG, "Thumbnail loading failed", e); - mView.showDefaultTextPreview(); - isEditableImage = false; - } - } else { - mView.showDefaultTextPreview(); - } - return isEditableImage; - } - private void animateIn() { if (mEnterAnimator != null && mEnterAnimator.isRunning()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java index 25caaeac2c38..758a6563323e 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java @@ -88,25 +88,4 @@ class ClipboardOverlayUtils { } return actions; } - - public Optional<RemoteAction> getAction(ClipData.Item item, String source) { - return getActions(item).stream().filter(remoteAction -> { - ComponentName component = remoteAction.getActionIntent().getIntent().getComponent(); - return component != null && !TextUtils.equals(source, component.getPackageName()); - }).findFirst(); - } - - private ArrayList<RemoteAction> getActions(ClipData.Item item) { - ArrayList<RemoteAction> actions = new ArrayList<>(); - for (TextLinks.TextLink link : item.getTextLinks().getLinks()) { - // skip classification for incidental entities - if (link.getEnd() - link.getStart() - >= item.getText().length() * MINIMUM_ENTITY_PROPORTION) { - TextClassification classification = mTextClassifier.classifyText( - item.getText(), link.getStart(), link.getEnd(), null); - actions.addAll(classification.getActions()); - } - } - return actions; - } } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt new file mode 100644 index 000000000000..81ed07653f26 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.ui.view + +import android.util.MathUtils +import android.view.MotionEvent + +/** + * Returns the distance from the raw position of this [MotionEvent] and the given coordinates. + * Because this is all expected to be in the coordinate space of the display and not the view, + * applying mutations to the view (such as scaling animations) does not affect the distance + * measured. + * @param xOnDisplay the x coordinate relative to the display + * @param yOnDisplay the y coordinate relative to the display + * @return distance from the raw position of this [MotionEvent] and the given coordinates + */ +fun MotionEvent.rawDistanceFrom( + xOnDisplay: Float, + yOnDisplay: Float, +): Float { + return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay) +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index df236e7f9c8f..9bf6b2a5b42b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -17,7 +17,6 @@ package com.android.systemui.dagger import com.android.keyguard.KeyguardBiometricLockoutLogger -import com.android.systemui.ChooserPinMigration import com.android.systemui.ChooserSelector import com.android.systemui.CoreStartable import com.android.systemui.LatencyTester @@ -76,13 +75,6 @@ abstract class SystemUICoreStartableModule { @ClassKey(AuthController::class) abstract fun bindAuthController(service: AuthController): CoreStartable - /** Inject into ChooserPinMigration. */ - @Binds - @IntoMap - @ClassKey(ChooserPinMigration::class) - @PerUser - abstract fun bindChooserPinMigration(sysui: ChooserPinMigration): CoreStartable - /** Inject into ChooserCoreStartable. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 63a4fd2189d8..7945470b424a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -85,6 +85,8 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.people.PeopleHubModule; import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent; import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent; @@ -116,16 +118,16 @@ import com.android.systemui.wallet.dagger.WalletModule; import com.android.systemui.wmshell.BubblesManager; import com.android.wm.shell.bubbles.Bubbles; -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Named; - import dagger.Binds; import dagger.BindsOptionalOf; import dagger.Module; import dagger.Provides; +import java.util.Optional; +import java.util.concurrent.Executor; + +import javax.inject.Named; + /** * A dagger module for injecting components of System UI that are required by System UI. * @@ -315,4 +317,11 @@ public abstract class SystemUIModule { @Binds abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator( LargeScreenShadeInterpolatorImpl impl); + + @SysUISingleton + @Provides + static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider( + NotificationInterruptStateProvider innerProvider) { + return new NotificationInterruptStateProviderWrapper(innerProvider); + } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt index 88c0c50d09a5..4e62104034ee 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt @@ -98,7 +98,8 @@ class FaceScanningProviderFactory @Inject constructor( } fun shouldShowFaceScanningAnim(): Boolean { - return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning + return canShowFaceScanningAnim() && + (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing) } } @@ -142,6 +143,7 @@ class FaceScanningOverlayProviderImpl( keyguardUpdateMonitor, mainExecutor, logger, + authController, ) view.id = viewId view.setColor(tintColor) 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 0bc8506f272d..69d758213f1e 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -65,7 +65,7 @@ object Flags { val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update") // TODO(b/254512538): Tracking Bug - val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply") + val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") // TODO(b/254512425): Tracking Bug val NOTIFICATION_MEMORY_MONITOR_ENABLED = @@ -103,6 +103,11 @@ object Flags { val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD = releasedFlag(254647461, "filter_unseen_notifs_on_keyguard") + // TODO(b/277338665): Tracking Bug + @JvmField + val NOTIFICATION_SHELF_REFACTOR = + unreleasedFlag(271161129, "notification_shelf_refactor") + // TODO(b/263414400): Tracking Bug @JvmField val NOTIFICATION_ANIMATE_BIG_PICTURE = @@ -132,6 +137,11 @@ object Flags { // TODO(b/254512676): Tracking Bug @JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks") + // TODO(b/275694445): Tracking Bug + @JvmField + val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = unreleasedFlag(208, + "lockscreen_without_secure_lock_when_dreaming") + /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. @@ -224,12 +234,18 @@ object Flags { /** Whether to inflate the bouncer view on a background thread. */ // TODO(b/273341787): Tracking Bug @JvmField - val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard") + val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true) /** Whether to use a new data source for intents to run on keyguard dismissal. */ @JvmField val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent") + /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */ + // TODO(b/277220285): Tracking bug. + @JvmField + val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP = + unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -395,7 +411,7 @@ object Flags { val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations") // TODO(b/270437894): Tracking Bug - val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume") + val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume", teamfood = true) // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging") @@ -605,13 +621,8 @@ object Flags { val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW = releasedFlag(1504, "sharesheet_scrollable_image_preview") - // TODO(b/274137694) Tracking Bug - val CHOOSER_MIGRATION_ENABLED = unreleasedFlag(1505, "chooser_migration_enabled") - // 1700 - clipboard @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior") - // TODO(b/267162944): Tracking bug - @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model") // 1800 - shade container @JvmField @@ -639,9 +650,6 @@ object Flags { val APP_PANELS_REMOVE_APPS_ALLOWED = unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true) - // 2100 - Falsing Manager - @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps") - // 2200 - udfps // TODO(b/259264861): Tracking Bug @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection") diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt index a173f8b914db..2ef5e19bf382 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt @@ -90,9 +90,9 @@ class KeyboardBacklightDialog( private fun updateResources() { context.resources.apply { - filledRectangleColor = getColor(R.color.backlight_indicator_step_filled) - emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty) - backgroundColor = getColor(R.color.backlight_indicator_background) + filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme) + emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme) + backgroundColor = getColor(R.color.backlight_indicator_background, context.theme) rootProperties = RootProperties( cornerRadius = @@ -224,7 +224,6 @@ class KeyboardBacklightDialog( private fun setWindowTitle() { val attrs = window.attributes - // TODO(b/271796169): check if title needs to be a translatable resource. attrs.title = "KeyboardBacklightDialog" attrs?.y = dialogBottomMargin window.attributes = attrs diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index c102c5b57375..416b2379eef8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -128,6 +128,8 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.dagger.KeyguardModule; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -1184,6 +1186,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator; private Lazy<ScrimController> mScrimControllerLazy; + private FeatureFlags mFeatureFlags; + /** * Injected constructor. See {@link KeyguardModule}. */ @@ -1214,7 +1218,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Lazy<ShadeController> shadeControllerLazy, Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, - Lazy<ScrimController> scrimControllerLazy) { + Lazy<ScrimController> scrimControllerLazy, + FeatureFlags featureFlags) { mContext = context; mUserTracker = userTracker; mFalsingCollector = falsingCollector; @@ -1269,6 +1274,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS; mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS; + + mFeatureFlags = featureFlags; } public void userActivity() { @@ -1682,14 +1689,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } /** - * A dream started. We should lock after the usual screen-off lock timeout but only - * if there is a secure lock pattern. + * A dream started. We should lock after the usual screen-off lock timeout regardless if + * there is a secure lock pattern or not */ public void onDreamingStarted() { mUpdateMonitor.dispatchDreamingStarted(); synchronized (this) { + final boolean alwaysShowKeyguard = + mFeatureFlags.isEnabled(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING); if (mDeviceInteractive - && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) { + && (alwaysShowKeyguard || + mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()))) { doKeyguardLaterLocked(); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 6ac51cd52b49..5e71458c8bb4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; @@ -119,7 +120,8 @@ public class KeyguardModule { Lazy<ShadeController> shadeController, Lazy<NotificationShadeWindowController> notificationShadeWindowController, Lazy<ActivityLaunchAnimator> activityLaunchAnimator, - Lazy<ScrimController> scrimControllerLazy) { + Lazy<ScrimController> scrimControllerLazy, + FeatureFlags featureFlags) { return new KeyguardViewMediator( context, userTracker, @@ -149,7 +151,8 @@ public class KeyguardModule { shadeController, notificationShadeWindowController, activityLaunchAnimator, - scrimControllerLazy); + scrimControllerLazy, + featureFlags); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index 8ece3183fd56..ab4abbf3d575 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository import android.content.Context import android.os.UserHandle +import android.util.LayoutDirection import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging @@ -113,30 +114,6 @@ constructor( initialValue = emptyMap(), ) - private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy { - fun parseSlot(unparsedSlot: String): Pair<String, Int> { - val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER) - check(split.size == 2) - val slotId = split[0] - val slotCapacity = split[1].toInt() - return slotId to slotCapacity - } - - val unparsedSlots = - appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots) - - val seenSlotIds = mutableSetOf<String>() - unparsedSlots.mapNotNull { unparsedSlot -> - val (slotId, slotCapacity) = parseSlot(unparsedSlot) - check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" } - seenSlotIds.add(slotId) - KeyguardSlotPickerRepresentation( - id = slotId, - maxSelectedAffordances = slotCapacity, - ) - } - } - init { legacySettingSyncer.startSyncing() dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster()) @@ -211,7 +188,30 @@ constructor( * each slot and select which affordance(s) is/are installed in each slot on the keyguard. */ fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { - return _slotPickerRepresentations + fun parseSlot(unparsedSlot: String): Pair<String, Int> { + val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER) + check(split.size == 2) + val slotId = split[0] + val slotCapacity = split[1].toInt() + return slotId to slotCapacity + } + + val unparsedSlots = + appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots) + if (appContext.resources.configuration.layoutDirection == LayoutDirection.RTL) { + unparsedSlots.reverse() + } + + val seenSlotIds = mutableSetOf<String>() + return unparsedSlots.mapNotNull { unparsedSlot -> + val (slotId, slotCapacity) = parseSlot(unparsedSlot) + check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" } + seenSlotIds.add(slotId) + KeyguardSlotPickerRepresentation( + id = slotId, + maxSelectedAffordances = slotCapacity, + ) + } } private inner class Dumpster : Dumpable { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt index 6525a13fc44f..ea6700e92731 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt @@ -17,29 +17,29 @@ package com.android.systemui.keyguard.domain.interactor -import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.view.accessibility.AccessibilityManager +import androidx.annotation.VisibleForTesting import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger -import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Business logic for use-cases related to the keyguard long-press feature. */ @OptIn(ExperimentalCoroutinesApi::class) @@ -54,18 +55,16 @@ import kotlinx.coroutines.flow.stateIn class KeyguardLongPressInteractor @Inject constructor( - @Application unsafeContext: Context, - @Application scope: CoroutineScope, + @Application private val scope: CoroutineScope, transitionInteractor: KeyguardTransitionInteractor, repository: KeyguardRepository, - private val activityStarter: ActivityStarter, private val logger: UiEventLogger, private val featureFlags: FeatureFlags, broadcastDispatcher: BroadcastDispatcher, + private val accessibilityManager: AccessibilityManagerWrapper, ) { - private val appContext = unsafeContext.applicationContext - - private val _isLongPressHandlingEnabled: StateFlow<Boolean> = + /** Whether the long-press handling feature should be enabled. */ + val isLongPressHandlingEnabled: StateFlow<Boolean> = if (isFeatureEnabled()) { combine( transitionInteractor.finishedKeyguardState.map { @@ -84,19 +83,35 @@ constructor( initialValue = false, ) - /** Whether the long-press handling feature should be enabled. */ - val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled - - private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null) - /** Model for a menu that should be shown; `null` when no menu should be shown. */ - val menu: Flow<KeyguardSettingsPopupMenuModel?> = - isLongPressHandlingEnabled.flatMapLatest { isEnabled -> - if (isEnabled) { - _menu - } else { - flowOf(null) + private val _isMenuVisible = MutableStateFlow(false) + /** Model for whether the menu should be shown. */ + val isMenuVisible: StateFlow<Boolean> = + isLongPressHandlingEnabled + .flatMapLatest { isEnabled -> + if (isEnabled) { + _isMenuVisible.asStateFlow() + } else { + // Reset the state so we don't see a menu when long-press handling is enabled + // again in the future. + _isMenuVisible.value = false + flowOf(false) + } } - } + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + + private val _shouldOpenSettings = MutableStateFlow(false) + /** + * Whether the long-press accessible "settings" flow should be opened. + * + * Note that [onSettingsShown] must be invoked to consume this, once the settings are opened. + */ + val shouldOpenSettings = _shouldOpenSettings.asStateFlow() + + private var delayedHideMenuJob: Job? = null init { if (isFeatureEnabled()) { @@ -110,15 +125,46 @@ constructor( } /** Notifies that the user has long-pressed on the lock screen. */ - fun onLongPress(x: Int, y: Int) { - if (!_isLongPressHandlingEnabled.value) { + fun onLongPress() { + if (!isLongPressHandlingEnabled.value) { return } - showMenu( - x = x, - y = y, - ) + if (featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP)) { + showSettings() + } else { + showMenu() + } + } + + /** Notifies that the user has touched outside of the pop-up. */ + fun onTouchedOutside() { + hideMenu() + } + + /** Notifies that the user has started a touch gesture on the menu. */ + fun onMenuTouchGestureStarted() { + cancelAutomaticMenuHiding() + } + + /** Notifies that the user has started a touch gesture on the menu. */ + fun onMenuTouchGestureEnded(isClick: Boolean) { + if (isClick) { + hideMenu() + logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) + showSettings() + } else { + scheduleAutomaticMenuHiding() + } + } + + /** Notifies that the settings UI has been shown, consuming the event to show it. */ + fun onSettingsShown() { + _shouldOpenSettings.value = false + } + + private fun showSettings() { + _shouldOpenSettings.value = true } private fun isFeatureEnabled(): Boolean { @@ -126,51 +172,40 @@ constructor( featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) } - /** Updates application state to ask to show the menu at the given coordinates. */ - private fun showMenu( - x: Int, - y: Int, - ) { - _menu.value = - KeyguardSettingsPopupMenuModel( - position = - Position( - x = x, - y = y, - ), - onClicked = { - hideMenu() - navigateToLockScreenSettings() - }, - onDismissed = { hideMenu() }, - ) + /** Updates application state to ask to show the menu. */ + private fun showMenu() { + _isMenuVisible.value = true + scheduleAutomaticMenuHiding() logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) } + private fun scheduleAutomaticMenuHiding() { + cancelAutomaticMenuHiding() + delayedHideMenuJob = + scope.launch { + delay(timeOutMs()) + hideMenu() + } + } + /** Updates application state to ask to hide the menu. */ private fun hideMenu() { - _menu.value = null + cancelAutomaticMenuHiding() + _isMenuVisible.value = false } - /** Opens the wallpaper picker screen after the device is unlocked by the user. */ - private fun navigateToLockScreenSettings() { - logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) - activityStarter.dismissKeyguardThenExecute( - /* action= */ { - appContext.startActivity( - Intent(Intent.ACTION_SET_WALLPAPER).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - appContext - .getString(R.string.config_wallpaperPickerPackage) - .takeIf { it.isNotEmpty() } - ?.let { packageName -> setPackage(packageName) } - } - ) - true - }, - /* cancel= */ {}, - /* afterKeyguardGone= */ true, - ) + private fun cancelAutomaticMenuHiding() { + delayedHideMenuJob?.cancel() + delayedHideMenuJob = null + } + + private fun timeOutMs(): Long { + return accessibilityManager + .getRecommendedTimeoutMillis( + DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS.toInt(), + AccessibilityManager.FLAG_CONTENT_ICONS or AccessibilityManager.FLAG_CONTENT_TEXT, + ) + .toLong() } enum class LogEvents( @@ -184,4 +219,8 @@ constructor( override fun getId() = _id } + + companion object { + @VisibleForTesting const val DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS = 5000L + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt deleted file mode 100644 index 7c61e7108265..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.domain.model - -import com.android.systemui.common.shared.model.Position - -/** Models a settings popup menu for the lock screen. */ -data class KeyguardSettingsPopupMenuModel( - /** Where the menu should be anchored, roughly in screen space. */ - val position: Position, - /** Callback to invoke when the menu gets clicked by the user. */ - val onClicked: () -> Unit, - /** Callback to invoke when the menu gets dismissed by the user. */ - val onDismissed: () -> Unit, -) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt new file mode 100644 index 000000000000..568db2f543b6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import android.os.VibrationEffect +import kotlin.time.Duration.Companion.milliseconds + +object KeyguardBottomAreaVibrations { + + val ShakeAnimationDuration = 300.milliseconds + const val ShakeAnimationCycles = 5f + + private const val SmallVibrationScale = 0.3f + private const val BigVibrationScale = 0.6f + + val Shake = + VibrationEffect.startComposition() + .apply { + val vibrationDelayMs = + (ShakeAnimationDuration.inWholeMilliseconds / ShakeAnimationCycles * 2).toInt() + val vibrationCount = ShakeAnimationCycles.toInt() * 2 + repeat(vibrationCount) { + addPrimitive( + VibrationEffect.Composition.PRIMITIVE_TICK, + SmallVibrationScale, + vibrationDelayMs, + ) + } + } + .compose() + + val Activated = + VibrationEffect.startComposition() + .addPrimitive( + VibrationEffect.Composition.PRIMITIVE_TICK, + BigVibrationScale, + 0, + ) + .addPrimitive( + VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, + 0.1f, + 0, + ) + .compose() + + val Deactivated = + VibrationEffect.startComposition() + .addPrimitive( + VibrationEffect.Composition.PRIMITIVE_TICK, + BigVibrationScale, + 0, + ) + .addPrimitive( + VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, + 0.1f, + 0, + ) + .compose() +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index d63636c6fccc..68ac7e1c71e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -17,41 +17,42 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint +import android.content.Intent +import android.graphics.Rect import android.graphics.drawable.Animatable2 -import android.os.VibrationEffect import android.util.Size import android.util.TypedValue -import android.view.MotionEvent import android.view.View -import android.view.ViewConfiguration import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.widget.ImageView import android.widget.TextView -import androidx.core.animation.CycleInterpolator -import androidx.core.animation.ObjectAnimator +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.settingslib.Utils import com.android.systemui.R +import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.animation.Interpolators +import com.android.systemui.animation.view.LaunchableLinearLayout import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.binder.IconViewBinder +import com.android.systemui.common.ui.binder.TextViewBinder import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper -import kotlin.math.pow -import kotlin.math.sqrt -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -91,15 +92,20 @@ object KeyguardBottomAreaViewBinder { * icon */ fun shouldConstrainToTopOfLockIcon(): Boolean + + /** Destroys this binding, releases resources, and cancels any coroutines. */ + fun destroy() } /** Binds the view to the view-model, continuing to update the former based on the latter. */ + @SuppressLint("ClickableViewAccessibility") @JvmStatic fun bind( view: ViewGroup, viewModel: KeyguardBottomAreaViewModel, falsingManager: FalsingManager?, vibratorHelper: VibratorHelper?, + activityStarter: ActivityStarter?, messageDisplayer: (Int) -> Unit, ): Binding { val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area) @@ -110,137 +116,192 @@ object KeyguardBottomAreaViewBinder { val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text) val indicationTextBottom: TextView = view.requireViewById(R.id.keyguard_indication_text_bottom) + val settingsMenu: LaunchableLinearLayout = + view.requireViewById(R.id.keyguard_settings_button) view.clipChildren = false view.clipToPadding = false + view.setOnTouchListener { _, event -> + if (settingsMenu.isVisible) { + val hitRect = Rect() + settingsMenu.getHitRect(hitRect) + if (!hitRect.contains(event.x.toInt(), event.y.toInt())) { + viewModel.onTouchedOutsideLockScreenSettingsMenu() + } + } + + false + } val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) - view.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - viewModel.startButton.collect { buttonModel -> - updateButton( + val disposableHandle = + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.startButton.collect { buttonModel -> + updateButton( + view = startButton, + viewModel = buttonModel, + falsingManager = falsingManager, + messageDisplayer = messageDisplayer, + vibratorHelper = vibratorHelper, + ) + } + } + + launch { + viewModel.endButton.collect { buttonModel -> + updateButton( + view = endButton, + viewModel = buttonModel, + falsingManager = falsingManager, + messageDisplayer = messageDisplayer, + vibratorHelper = vibratorHelper, + ) + } + } + + launch { + viewModel.isOverlayContainerVisible.collect { isVisible -> + overlayContainer.visibility = + if (isVisible) { + View.VISIBLE + } else { + View.INVISIBLE + } + } + } + + launch { + viewModel.alpha.collect { alpha -> + view.importantForAccessibility = + if (alpha == 0f) { + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + } else { + View.IMPORTANT_FOR_ACCESSIBILITY_AUTO + } + + ambientIndicationArea?.alpha = alpha + indicationArea.alpha = alpha + } + } + + launch { + updateButtonAlpha( view = startButton, - viewModel = buttonModel, - falsingManager = falsingManager, - messageDisplayer = messageDisplayer, - vibratorHelper = vibratorHelper, + viewModel = viewModel.startButton, + alphaFlow = viewModel.alpha, ) } - } - launch { - viewModel.endButton.collect { buttonModel -> - updateButton( + launch { + updateButtonAlpha( view = endButton, - viewModel = buttonModel, - falsingManager = falsingManager, - messageDisplayer = messageDisplayer, - vibratorHelper = vibratorHelper, + viewModel = viewModel.endButton, + alphaFlow = viewModel.alpha, ) } - } - launch { - viewModel.isOverlayContainerVisible.collect { isVisible -> - overlayContainer.visibility = - if (isVisible) { - View.VISIBLE - } else { - View.INVISIBLE - } + launch { + viewModel.indicationAreaTranslationX.collect { translationX -> + indicationArea.translationX = translationX + ambientIndicationArea?.translationX = translationX + } } - } - launch { - viewModel.alpha.collect { alpha -> - view.importantForAccessibility = - if (alpha == 0f) { - View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - } else { - View.IMPORTANT_FOR_ACCESSIBILITY_AUTO + launch { + combine( + viewModel.isIndicationAreaPadded, + configurationBasedDimensions.map { it.indicationAreaPaddingPx }, + ) { isPadded, paddingIfPaddedPx -> + if (isPadded) { + paddingIfPaddedPx + } else { + 0 + } + } + .collect { paddingPx -> + indicationArea.setPadding(paddingPx, 0, paddingPx, 0) } - - ambientIndicationArea?.alpha = alpha - indicationArea.alpha = alpha } - } - - launch { - updateButtonAlpha( - view = startButton, - viewModel = viewModel.startButton, - alphaFlow = viewModel.alpha, - ) - } - - launch { - updateButtonAlpha( - view = endButton, - viewModel = viewModel.endButton, - alphaFlow = viewModel.alpha, - ) - } - launch { - viewModel.indicationAreaTranslationX.collect { translationX -> - indicationArea.translationX = translationX - ambientIndicationArea?.translationX = translationX + launch { + configurationBasedDimensions + .map { it.defaultBurnInPreventionYOffsetPx } + .flatMapLatest { defaultBurnInOffsetY -> + viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) + } + .collect { translationY -> + indicationArea.translationY = translationY + ambientIndicationArea?.translationY = translationY + } } - } - launch { - combine( - viewModel.isIndicationAreaPadded, - configurationBasedDimensions.map { it.indicationAreaPaddingPx }, - ) { isPadded, paddingIfPaddedPx -> - if (isPadded) { - paddingIfPaddedPx - } else { - 0 + launch { + configurationBasedDimensions.collect { dimensions -> + indicationText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + dimensions.indicationTextSizePx.toFloat(), + ) + indicationTextBottom.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + dimensions.indicationTextSizePx.toFloat(), + ) + + startButton.updateLayoutParams<ViewGroup.LayoutParams> { + width = dimensions.buttonSizePx.width + height = dimensions.buttonSizePx.height + } + endButton.updateLayoutParams<ViewGroup.LayoutParams> { + width = dimensions.buttonSizePx.width + height = dimensions.buttonSizePx.height } } - .collect { paddingPx -> - indicationArea.setPadding(paddingPx, 0, paddingPx, 0) - } - } + } - launch { - configurationBasedDimensions - .map { it.defaultBurnInPreventionYOffsetPx } - .flatMapLatest { defaultBurnInOffsetY -> - viewModel.indicationAreaTranslationY(defaultBurnInOffsetY) - } - .collect { translationY -> - indicationArea.translationY = translationY - ambientIndicationArea?.translationY = translationY + launch { + viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect { + isVisible -> + settingsMenu.animateVisibility(visible = isVisible) + if (isVisible) { + vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated) + settingsMenu.setOnTouchListener( + KeyguardSettingsButtonOnTouchListener( + view = settingsMenu, + viewModel = viewModel.settingsMenuViewModel, + ) + ) + IconViewBinder.bind( + icon = viewModel.settingsMenuViewModel.icon, + view = settingsMenu.requireViewById(R.id.icon), + ) + TextViewBinder.bind( + view = settingsMenu.requireViewById(R.id.text), + viewModel = viewModel.settingsMenuViewModel.text, + ) + } } - } - - launch { - configurationBasedDimensions.collect { dimensions -> - indicationText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - dimensions.indicationTextSizePx.toFloat(), - ) - indicationTextBottom.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - dimensions.indicationTextSizePx.toFloat(), - ) + } - startButton.updateLayoutParams<ViewGroup.LayoutParams> { - width = dimensions.buttonSizePx.width - height = dimensions.buttonSizePx.height - } - endButton.updateLayoutParams<ViewGroup.LayoutParams> { - width = dimensions.buttonSizePx.width - height = dimensions.buttonSizePx.height + // activityStarter will only be null when rendering the preview that + // shows up in the Wallpaper Picker app. If we do that, then the + // settings menu should never be visible. + if (activityStarter != null) { + launch { + viewModel.settingsMenuViewModel.shouldOpenSettings + .filter { it } + .collect { + navigateToLockScreenSettings( + activityStarter = activityStarter, + view = settingsMenu, + ) + viewModel.settingsMenuViewModel.onSettingsShown() + } } } } } - } return object : Binding { override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> { @@ -253,6 +314,10 @@ object KeyguardBottomAreaViewBinder { override fun shouldConstrainToTopOfLockIcon(): Boolean = viewModel.shouldConstrainToTopOfLockIcon() + + override fun destroy() { + disposableHandle.dispose() + } } } @@ -265,7 +330,7 @@ object KeyguardBottomAreaViewBinder { vibratorHelper: VibratorHelper?, ) { if (!viewModel.isVisible) { - view.isVisible = false + view.isInvisible = true return } @@ -342,7 +407,7 @@ object KeyguardBottomAreaViewBinder { if (viewModel.isClickable) { if (viewModel.useLongPress) { view.setOnTouchListener( - OnTouchListener( + KeyguardQuickAffordanceOnTouchListener( view, viewModel, messageDisplayer, @@ -372,187 +437,21 @@ object KeyguardBottomAreaViewBinder { .collect { view.alpha = it } } - private class OnTouchListener( - private val view: View, - private val viewModel: KeyguardQuickAffordanceViewModel, - private val messageDisplayer: (Int) -> Unit, - private val vibratorHelper: VibratorHelper?, - private val falsingManager: FalsingManager?, - ) : View.OnTouchListener { - - private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong() - private var longPressAnimator: ViewPropertyAnimator? = null - - @SuppressLint("ClickableViewAccessibility") - override fun onTouch(v: View?, event: MotionEvent?): Boolean { - return when (event?.actionMasked) { - MotionEvent.ACTION_DOWN -> - if (viewModel.configKey != null) { - if (isUsingAccurateTool(event)) { - // For accurate tool types (stylus, mouse, etc.), we don't require a - // long-press. - } else { - // When not using a stylus, we require a long-press to activate the - // quick affordance, mostly to do "falsing" (e.g. protect from false - // clicks in the pocket/bag). - longPressAnimator = - view - .animate() - .scaleX(PRESSED_SCALE) - .scaleY(PRESSED_SCALE) - .setDuration(longPressDurationMs) - .withEndAction { - if ( - falsingManager - ?.isFalseLongTap( - FalsingManager.MODERATE_PENALTY - ) == false - ) { - dispatchClick(viewModel.configKey) - } - cancel() - } - } - true - } else { - false - } - MotionEvent.ACTION_MOVE -> { - if (!isUsingAccurateTool(event)) { - // Moving too far while performing a long-press gesture cancels that - // gesture. - val distanceMoved = distanceMoved(event) - if (distanceMoved > ViewConfiguration.getTouchSlop()) { - cancel() - } - } - true - } - MotionEvent.ACTION_UP -> { - if (isUsingAccurateTool(event)) { - // When using an accurate tool type (stylus, mouse, etc.), we don't require - // a long-press gesture to activate the quick affordance. Therefore, lifting - // the pointer performs a click. - if ( - viewModel.configKey != null && - distanceMoved(event) <= ViewConfiguration.getTouchSlop() && - falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false - ) { - dispatchClick(viewModel.configKey) - } - } else { - // When not using a stylus, lifting the finger/pointer will actually cancel - // the long-press gesture. Calling cancel after the quick affordance was - // already long-press activated is a no-op, so it's safe to call from here. - cancel( - onAnimationEnd = - if (event.eventTime - event.downTime < longPressDurationMs) { - Runnable { - messageDisplayer.invoke( - R.string.keyguard_affordance_press_too_short - ) - val amplitude = - view.context.resources - .getDimensionPixelSize( - R.dimen.keyguard_affordance_shake_amplitude - ) - .toFloat() - val shakeAnimator = - ObjectAnimator.ofFloat( - view, - "translationX", - -amplitude / 2, - amplitude / 2, - ) - shakeAnimator.duration = - ShakeAnimationDuration.inWholeMilliseconds - shakeAnimator.interpolator = - CycleInterpolator(ShakeAnimationCycles) - shakeAnimator.start() - - vibratorHelper?.vibrate(Vibrations.Shake) - } - } else { - null - } - ) - } - true - } - MotionEvent.ACTION_CANCEL -> { - cancel() - true - } - else -> false - } - } - - private fun dispatchClick( - configKey: String, - ) { - view.setOnClickListener { - vibratorHelper?.vibrate( - if (viewModel.isActivated) { - Vibrations.Activated - } else { - Vibrations.Deactivated - } - ) - viewModel.onClicked( - KeyguardQuickAffordanceViewModel.OnClickedParameters( - configKey = configKey, - expandable = Expandable.fromView(view), - slotId = viewModel.slotId, - ) - ) - } - view.performClick() - view.setOnClickListener(null) - } - - private fun cancel(onAnimationEnd: Runnable? = null) { - longPressAnimator?.cancel() - longPressAnimator = null - view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) - } - - companion object { - private const val PRESSED_SCALE = 1.5f - - /** - * Returns `true` if the tool type at the given pointer index is an accurate tool (like - * stylus or mouse), which means we can trust it to not be a false click; `false` - * otherwise. - */ - private fun isUsingAccurateTool( - event: MotionEvent, - pointerIndex: Int = 0, - ): Boolean { - return when (event.getToolType(pointerIndex)) { - MotionEvent.TOOL_TYPE_STYLUS -> true - MotionEvent.TOOL_TYPE_MOUSE -> true - else -> false + private fun View.animateVisibility(visible: Boolean) { + animate() + .withStartAction { + if (visible) { + alpha = 0f + isVisible = true } } - - /** - * Returns the amount of distance the pointer moved since the historical record at the - * [since] index. - */ - private fun distanceMoved( - event: MotionEvent, - since: Int = 0, - ): Float { - return if (event.historySize > 0) { - sqrt( - (event.y - event.getHistoricalY(since)).pow(2) + - (event.x - event.getHistoricalX(since)).pow(2) - ) - } else { - 0f + .alpha(if (visible) 1f else 0f) + .withEndAction { + if (!visible) { + isVisible = false } } - } + .start() } private class OnClickListener( @@ -594,64 +493,28 @@ object KeyguardBottomAreaViewBinder { ) } + /** Opens the wallpaper picker screen after the device is unlocked by the user. */ + private fun navigateToLockScreenSettings( + activityStarter: ActivityStarter, + view: View, + ) { + activityStarter.startActivity( + Intent(Intent.ACTION_SET_WALLPAPER).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + view.context + .getString(R.string.config_wallpaperPickerPackage) + .takeIf { it.isNotEmpty() } + ?.let { packageName -> setPackage(packageName) } + }, + /* dismissShade= */ true, + ActivityLaunchAnimator.Controller.fromView(view), + ) + } + private data class ConfigurationBasedDimensions( val defaultBurnInPreventionYOffsetPx: Int, val indicationAreaPaddingPx: Int, val indicationTextSizePx: Int, val buttonSizePx: Size, ) - - private val ShakeAnimationDuration = 300.milliseconds - private val ShakeAnimationCycles = 5f - - object Vibrations { - - private const val SmallVibrationScale = 0.3f - private const val BigVibrationScale = 0.6f - - val Shake = - VibrationEffect.startComposition() - .apply { - val vibrationDelayMs = - (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2)) - .toInt() - val vibrationCount = ShakeAnimationCycles.toInt() * 2 - repeat(vibrationCount) { - addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - SmallVibrationScale, - vibrationDelayMs, - ) - } - } - .compose() - - val Activated = - VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, - 0.1f, - 0, - ) - .compose() - - val Deactivated = - VibrationEffect.startComposition() - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_TICK, - BigVibrationScale, - 0, - ) - .addPrimitive( - VibrationEffect.Composition.PRIMITIVE_QUICK_FALL, - 0.1f, - 0, - ) - .compose() - } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt deleted file mode 100644 index d85682b3bab8..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.binder - -import android.annotation.SuppressLint -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.view.WindowManager -import android.widget.PopupWindow -import com.android.systemui.R -import com.android.systemui.common.ui.binder.IconViewBinder -import com.android.systemui.common.ui.binder.TextViewBinder -import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel - -object KeyguardLongPressPopupViewBinder { - @SuppressLint("InflateParams") // We don't care that the parent is null. - fun createAndShow( - container: View, - viewModel: KeyguardSettingsPopupMenuViewModel, - onDismissed: () -> Unit, - ): () -> Unit { - val contentView: View = - LayoutInflater.from(container.context) - .inflate( - R.layout.keyguard_settings_popup_menu, - null, - ) - - contentView.setOnClickListener { viewModel.onClicked() } - IconViewBinder.bind( - icon = viewModel.icon, - view = contentView.requireViewById(R.id.icon), - ) - TextViewBinder.bind( - view = contentView.requireViewById(R.id.text), - viewModel = viewModel.text, - ) - - val popupWindow = - PopupWindow(container.context).apply { - windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG - setBackgroundDrawable(null) - animationStyle = com.android.internal.R.style.Animation_Dialog - isOutsideTouchable = true - isFocusable = true - setContentView(contentView) - setOnDismissListener { onDismissed() } - contentView.measure( - View.MeasureSpec.makeMeasureSpec( - 0, - View.MeasureSpec.UNSPECIFIED, - ), - View.MeasureSpec.makeMeasureSpec( - 0, - View.MeasureSpec.UNSPECIFIED, - ), - ) - showAtLocation( - container, - Gravity.NO_GRAVITY, - viewModel.position.x - contentView.measuredWidth / 2, - viewModel.position.y - - contentView.measuredHeight - - container.context.resources.getDimensionPixelSize( - R.dimen.keyguard_long_press_settings_popup_vertical_offset - ), - ) - } - - return { popupWindow.dismiss() } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt index 86717537efd3..9cc503c07955 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -50,10 +50,7 @@ object KeyguardLongPressViewBinder { return } - viewModel.onLongPress( - x = x, - y = y, - ) + viewModel.onLongPress() } override fun onSingleTapDetected(view: View) { @@ -72,23 +69,6 @@ object KeyguardLongPressViewBinder { view.setLongPressHandlingEnabled(isEnabled) } } - - launch { - var dismissMenu: (() -> Unit)? = null - - viewModel.menu.collect { menuOrNull -> - if (menuOrNull != null) { - dismissMenu = - KeyguardLongPressPopupViewBinder.createAndShow( - container = view, - viewModel = menuOrNull, - onDismissed = menuOrNull.onDismissed, - ) - } else { - dismissMenu?.invoke() - } - } - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt new file mode 100644 index 000000000000..5745d6ae077e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import android.annotation.SuppressLint +import android.graphics.PointF +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import android.view.ViewPropertyAnimator +import androidx.core.animation.CycleInterpolator +import androidx.core.animation.ObjectAnimator +import com.android.systemui.R +import com.android.systemui.animation.Expandable +import com.android.systemui.common.ui.view.rawDistanceFrom +import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.VibratorHelper + +class KeyguardQuickAffordanceOnTouchListener( + private val view: View, + private val viewModel: KeyguardQuickAffordanceViewModel, + private val messageDisplayer: (Int) -> Unit, + private val vibratorHelper: VibratorHelper?, + private val falsingManager: FalsingManager?, +) : View.OnTouchListener { + + private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong() + private var longPressAnimator: ViewPropertyAnimator? = null + private val downDisplayCoords: PointF by lazy { PointF() } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + return when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> + if (viewModel.configKey != null) { + downDisplayCoords.set(event.rawX, event.rawY) + if (isUsingAccurateTool(event)) { + // For accurate tool types (stylus, mouse, etc.), we don't require a + // long-press. + } else { + // When not using a stylus, we require a long-press to activate the + // quick affordance, mostly to do "falsing" (e.g. protect from false + // clicks in the pocket/bag). + longPressAnimator = + view + .animate() + .scaleX(PRESSED_SCALE) + .scaleY(PRESSED_SCALE) + .setDuration(longPressDurationMs) + .withEndAction { + if ( + falsingManager?.isFalseLongTap( + FalsingManager.MODERATE_PENALTY + ) == false + ) { + dispatchClick(viewModel.configKey) + } + cancel() + } + } + true + } else { + false + } + MotionEvent.ACTION_MOVE -> { + if (!isUsingAccurateTool(event)) { + // Moving too far while performing a long-press gesture cancels that + // gesture. + if ( + event + .rawDistanceFrom( + downDisplayCoords.x, + downDisplayCoords.y, + ) > ViewConfiguration.getTouchSlop() + ) { + cancel() + } + } + true + } + MotionEvent.ACTION_UP -> { + if (isUsingAccurateTool(event)) { + // When using an accurate tool type (stylus, mouse, etc.), we don't require + // a long-press gesture to activate the quick affordance. Therefore, lifting + // the pointer performs a click. + if ( + viewModel.configKey != null && + event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <= + ViewConfiguration.getTouchSlop() && + falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false + ) { + dispatchClick(viewModel.configKey) + } + } else { + // When not using a stylus, lifting the finger/pointer will actually cancel + // the long-press gesture. Calling cancel after the quick affordance was + // already long-press activated is a no-op, so it's safe to call from here. + cancel( + onAnimationEnd = + if (event.eventTime - event.downTime < longPressDurationMs) { + Runnable { + messageDisplayer.invoke( + R.string.keyguard_affordance_press_too_short + ) + val amplitude = + view.context.resources + .getDimensionPixelSize( + R.dimen.keyguard_affordance_shake_amplitude + ) + .toFloat() + val shakeAnimator = + ObjectAnimator.ofFloat( + view, + "translationX", + -amplitude / 2, + amplitude / 2, + ) + shakeAnimator.duration = + KeyguardBottomAreaVibrations.ShakeAnimationDuration + .inWholeMilliseconds + shakeAnimator.interpolator = + CycleInterpolator( + KeyguardBottomAreaVibrations.ShakeAnimationCycles + ) + shakeAnimator.start() + + vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake) + } + } else { + null + } + ) + } + true + } + MotionEvent.ACTION_CANCEL -> { + cancel() + true + } + else -> false + } + } + + private fun dispatchClick( + configKey: String, + ) { + view.setOnClickListener { + vibratorHelper?.vibrate( + if (viewModel.isActivated) { + KeyguardBottomAreaVibrations.Activated + } else { + KeyguardBottomAreaVibrations.Deactivated + } + ) + viewModel.onClicked( + KeyguardQuickAffordanceViewModel.OnClickedParameters( + configKey = configKey, + expandable = Expandable.fromView(view), + slotId = viewModel.slotId, + ) + ) + } + view.performClick() + view.setOnClickListener(null) + } + + private fun cancel(onAnimationEnd: Runnable? = null) { + longPressAnimator?.cancel() + longPressAnimator = null + view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd) + } + + companion object { + private const val PRESSED_SCALE = 1.5f + + /** + * Returns `true` if the tool type at the given pointer index is an accurate tool (like + * stylus or mouse), which means we can trust it to not be a false click; `false` otherwise. + */ + private fun isUsingAccurateTool( + event: MotionEvent, + pointerIndex: Int = 0, + ): Boolean { + return when (event.getToolType(pointerIndex)) { + MotionEvent.TOOL_TYPE_STYLUS -> true + MotionEvent.TOOL_TYPE_MOUSE -> true + else -> false + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt new file mode 100644 index 000000000000..c54203c97013 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.binder + +import android.graphics.PointF +import android.view.MotionEvent +import android.view.View +import android.view.ViewConfiguration +import com.android.systemui.animation.view.LaunchableLinearLayout +import com.android.systemui.common.ui.view.rawDistanceFrom +import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel + +class KeyguardSettingsButtonOnTouchListener( + private val view: LaunchableLinearLayout, + private val viewModel: KeyguardSettingsMenuViewModel, +) : View.OnTouchListener { + + private val downPositionDisplayCoords = PointF() + + override fun onTouch(view: View, motionEvent: MotionEvent): Boolean { + when (motionEvent.actionMasked) { + MotionEvent.ACTION_DOWN -> { + view.isPressed = true + downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY) + viewModel.onTouchGestureStarted() + } + MotionEvent.ACTION_UP -> { + view.isPressed = false + val distanceMoved = + motionEvent + .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y) + val isClick = distanceMoved < ViewConfiguration.getTouchSlop() + viewModel.onTouchGestureEnded(isClick) + if (isClick) { + view.performClick() + } + } + MotionEvent.ACTION_CANCEL -> { + view.isPressed = false + viewModel.onTouchGestureEnded(/* isClick= */ false) + } + } + + return true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index a8e346477690..2d83be95183e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -44,6 +44,8 @@ constructor( private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor, private val bottomAreaInteractor: KeyguardBottomAreaInteractor, private val burnInHelperWrapper: BurnInHelperWrapper, + private val longPressViewModel: KeyguardLongPressViewModel, + val settingsMenuViewModel: KeyguardSettingsMenuViewModel, ) { data class PreviewMode( val isInPreviewMode: Boolean = false, @@ -161,6 +163,14 @@ constructor( selectedPreviewSlotId.value = slotId } + /** + * Notifies that some input gesture has started somewhere in the bottom area that's outside of + * the lock screen settings menu item pop-up. + */ + fun onTouchedOutsideLockScreenSettingsMenu() { + longPressViewModel.onTouchedOutside() + } + private fun button( position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceViewModel> { @@ -225,9 +235,10 @@ constructor( isDimmed = isDimmed, slotId = slotId, ) - is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel( - slotId = slotId, - ) + is KeyguardQuickAffordanceModel.Hidden -> + KeyguardQuickAffordanceViewModel( + slotId = slotId, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt index d896390fd471..c73931a12455 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt @@ -17,15 +17,13 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.R -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map /** Models UI state to support the lock screen long-press feature. */ +@SysUISingleton class KeyguardLongPressViewModel @Inject constructor( @@ -35,35 +33,16 @@ constructor( /** Whether the long-press handling feature should be enabled. */ val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled - /** View-model for a menu that should be shown; `null` when no menu should be shown. */ - val menu: Flow<KeyguardSettingsPopupMenuViewModel?> = - interactor.menu.map { model -> - model?.let { - KeyguardSettingsPopupMenuViewModel( - icon = - Icon.Resource( - res = R.drawable.ic_settings, - contentDescription = null, - ), - text = - Text.Resource( - res = R.string.lock_screen_settings, - ), - position = model.position, - onClicked = model.onClicked, - onDismissed = model.onDismissed, - ) - } - } - /** Notifies that the user has long-pressed on the lock screen. */ - fun onLongPress( - x: Int, - y: Int, - ) { - interactor.onLongPress( - x = x, - y = y, - ) + fun onLongPress() { + interactor.onLongPress() + } + + /** + * Notifies that some input gesture has started somewhere outside of the lock screen settings + * menu item pop-up. + */ + fun onTouchedOutside() { + interactor.onTouchedOutside() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt new file mode 100644 index 000000000000..c36da9da58a5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.R +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +/** Models the UI state of a keyguard settings popup menu. */ +class KeyguardSettingsMenuViewModel +@Inject +constructor( + private val interactor: KeyguardLongPressInteractor, +) { + val isVisible: Flow<Boolean> = interactor.isMenuVisible + val shouldOpenSettings: Flow<Boolean> = interactor.shouldOpenSettings + + val icon: Icon = + Icon.Resource( + res = R.drawable.ic_palette, + contentDescription = null, + ) + + val text: Text = + Text.Resource( + res = R.string.lock_screen_settings, + ) + + fun onTouchGestureStarted() { + interactor.onMenuTouchGestureStarted() + } + + fun onTouchGestureEnded(isClick: Boolean) { + interactor.onMenuTouchGestureEnded( + isClick = isClick, + ) + } + + fun onSettingsShown() { + interactor.onSettingsShown() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt deleted file mode 100644 index 0571b05b4751..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.shared.model.Position -import com.android.systemui.common.shared.model.Text - -/** Models the UI state of a keyguard settings popup menu. */ -data class KeyguardSettingsPopupMenuViewModel( - val icon: Icon, - val text: Text, - /** Where the menu should be anchored, roughly in screen space. */ - val position: Position, - /** Callback to invoke when the menu gets clicked by the user. */ - val onClicked: () -> Unit, - /** Callback to invoke when the menu gets dismissed by the user. */ - val onDismissed: () -> Unit, -) diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 9d2d3553db6d..faaa205b15c2 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -18,7 +18,7 @@ package com.android.systemui.log.table import android.os.Trace import com.android.systemui.Dumpable -import com.android.systemui.plugins.util.RingBuffer +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.text.SimpleDateFormat diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java index 4f5bbb797516..81de607f24f8 100644 --- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java +++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java @@ -239,7 +239,7 @@ public class LogAccessDialogActivity extends Activity implements if (view.getId() == R.id.log_access_dialog_allow_button) { mCallback.approveAccessForClient(mUid, mPackageName); finish(); - } else if (view.getId() == R.id.log_access_dialog_allow_button) { + } else if (view.getId() == R.id.log_access_dialog_deny_button) { declineLogAccess(); finish(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java index c4e76b203f19..ccddd1d359b7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java @@ -60,7 +60,7 @@ import javax.inject.Inject; import dagger.Lazy; public class MediaProjectionPermissionActivity extends Activity - implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { + implements DialogInterface.OnClickListener { private static final String TAG = "MediaProjectionPermissionActivity"; private static final float MAX_APP_NAME_SIZE_PX = 500f; private static final String ELLIPSIS = "\u2026"; @@ -215,7 +215,8 @@ public class MediaProjectionPermissionActivity extends Activity SystemUIDialog.applyFlags(dialog); SystemUIDialog.setDialogSize(dialog); - dialog.setOnCancelListener(this); + dialog.setOnCancelListener(this::onDialogDismissedOrCancelled); + dialog.setOnDismissListener(this::onDialogDismissedOrCancelled); dialog.create(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); @@ -283,9 +284,10 @@ public class MediaProjectionPermissionActivity extends Activity return intent; } - @Override - public void onCancel(DialogInterface dialog) { - finish(); + private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) { + if (!isFinishing()) { + finish(); + } } private boolean isPartialScreenSharingEnabled() { 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/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt index 44855fb7c8cc..0f38d32e0b64 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt @@ -18,9 +18,11 @@ package com.android.systemui.notetask.shortcut import android.content.Context import android.content.Intent -import android.content.pm.UserInfo +import android.os.Build import android.os.Bundle +import android.os.UserHandle import android.os.UserManager +import android.util.Log import androidx.activity.ComponentActivity import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint @@ -63,9 +65,13 @@ constructor( // | Bubble#showOrHideAppBubble | <-------------- // | (with WP user ID) | // ---------------------------- - val mainUser: UserInfo? = userTracker.userProfiles.firstOrNull { it.isMain } - if (userManager.isManagedProfile && mainUser != null) { - controller.startNoteTaskProxyActivityForUser(mainUser.userHandle) + val mainUser: UserHandle? = userManager.mainUser + if (userManager.isManagedProfile) { + if (mainUser == null) { + logDebug { "Can't find the main user. Skipping the notes app launch." } + } else { + controller.startNoteTaskProxyActivityForUser(mainUser) + } } else { controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT) } @@ -83,3 +89,8 @@ constructor( } } } + +/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */ +private inline fun Any.logDebug(message: () -> String) { + if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c2c1306d2a32..a765702a95b2 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -18,6 +18,10 @@ package com.android.systemui.power; import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; + import android.app.Dialog; import android.app.KeyguardManager; import android.app.Notification; @@ -691,7 +695,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { d.setTitle(R.string.battery_saver_confirmation_title); d.setPositiveButton(R.string.battery_saver_confirmation_ok, (dialog, which) -> { - setSaverMode(true, false); + setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION); logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK); }); d.setNegativeButton(android.R.string.cancel, (dialog, which) -> @@ -790,8 +794,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { return builder; } - private void setSaverMode(boolean mode, boolean needFirstTimeWarning) { - BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning); + private void setSaverMode(boolean mode, boolean needFirstTimeWarning, + @SaverManualEnabledReason int reason) { + BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason); } private void startBatterySaverSchedulePage() { @@ -839,7 +844,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } else if (action.equals(ACTION_START_SAVER)) { logEvent(BatteryWarningEvents .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON); - setSaverMode(true, true); + setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING); dismissLowBatteryNotification(); } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) { dismissLowBatteryNotification(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index ce690e239da0..57b479e6899f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -26,8 +26,6 @@ import com.android.systemui.R; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; -import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; -import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; @@ -35,7 +33,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -public interface QSHost extends PanelInteractor, CustomTileAddedRepository { +public interface QSHost { String TILES_SETTING = Settings.Secure.QS_TILES; int POSITION_AT_END = -1; @@ -75,7 +73,11 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository { * @see QSFactory#createTileView */ QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView); - /** Create a {@link QSTile} of a {@code tileSpec} type. */ + /** Create a {@link QSTile} of a {@code tileSpec} type. + * + * This should only be called by classes that need to create one-off instances of tiles. + * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle. + */ QSTile createTile(String tileSpec); /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt new file mode 100644 index 000000000000..14acb4b3ccf4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.content.ComponentName +import android.content.Context +import androidx.annotation.GuardedBy +import com.android.internal.logging.InstanceId +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.qs.external.TileServiceRequestController +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * Adapter to determine what real class to use for classes that depend on [QSHost]. + * + * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost]. + * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be + * routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to + * [QSTileHost]. + * + * This routing also includes dumps. + */ +@SysUISingleton +class QSHostAdapter +@Inject +constructor( + private val qsTileHost: QSTileHost, + private val interactor: CurrentTilesInteractor, + private val context: Context, + private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder, + @Application private val scope: CoroutineScope, + private val featureFlags: FeatureFlags, + dumpManager: DumpManager, +) : QSHost { + + companion object { + private const val TAG = "QSTileHost" + } + + private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST) + + @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>() + + init { + scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() } + // Redirect dump to the correct host (needed for CTS tests) + dumpManager.registerCriticalDumpable( + TAG, + if (useNewHost) interactor else qsTileHost + ) + } + + override fun getTiles(): Collection<QSTile> { + return if (useNewHost) { + interactor.currentQSTiles + } else { + qsTileHost.getTiles() + } + } + + override fun getSpecs(): List<String> { + return if (useNewHost) { + interactor.currentTilesSpecs.map { it.spec } + } else { + qsTileHost.getSpecs() + } + } + + override fun removeTile(spec: String) { + if (useNewHost) { + interactor.removeTiles(listOf(TileSpec.create(spec))) + } else { + qsTileHost.removeTile(spec) + } + } + + override fun addCallback(callback: QSHost.Callback) { + if (useNewHost) { + val job = + scope.launch { + interactor.currentTiles.collect { callback.onTilesChanged() } + } + synchronized(callbacksMap) { callbacksMap.put(callback, job) } + } else { + qsTileHost.addCallback(callback) + } + } + + override fun removeCallback(callback: QSHost.Callback) { + if (useNewHost) { + synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() } + } else { + qsTileHost.removeCallback(callback) + } + } + + override fun removeTiles(specs: Collection<String>) { + if (useNewHost) { + interactor.removeTiles(specs.map(TileSpec::create)) + } else { + qsTileHost.removeTiles(specs) + } + } + + override fun removeTileByUser(component: ComponentName) { + if (useNewHost) { + interactor.removeTiles(listOf(TileSpec.create(component))) + } else { + qsTileHost.removeTileByUser(component) + } + } + + override fun addTile(spec: String, position: Int) { + if (useNewHost) { + interactor.addTile(TileSpec.create(spec), position) + } else { + qsTileHost.addTile(spec, position) + } + } + + override fun addTile(component: ComponentName, end: Boolean) { + if (useNewHost) { + interactor.addTile( + TileSpec.create(component), + if (end) POSITION_AT_END else 0 + ) + } else { + qsTileHost.addTile(component, end) + } + } + + override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) { + if (useNewHost) { + interactor.setTiles(newTiles.map(TileSpec::create)) + } else { + qsTileHost.changeTilesByUser(previousTiles, newTiles) + } + } + + override fun warn(message: String?, t: Throwable?) { + qsTileHost.warn(message, t) + } + + override fun getContext(): Context { + return if (useNewHost) { + context + } else { + qsTileHost.context + } + } + + override fun getUserContext(): Context { + return if (useNewHost) { + interactor.userContext.value + } else { + qsTileHost.userContext + } + } + + override fun getUserId(): Int { + return if (useNewHost) { + interactor.userId.value + } else { + qsTileHost.userId + } + } + + override fun getUiEventLogger(): UiEventLogger { + return qsTileHost.uiEventLogger + } + + override fun createTileView( + themedContext: Context?, + tile: QSTile?, + collapsedView: Boolean + ): QSTileView { + return qsTileHost.createTileView(themedContext, tile, collapsedView) + } + + override fun createTile(tileSpec: String): QSTile? { + return qsTileHost.createTile(tileSpec) + } + + override fun addTile(spec: String) { + return addTile(spec, QSHost.POSITION_AT_END) + } + + override fun addTile(tile: ComponentName) { + return addTile(tile, false) + } + + override fun indexOf(tileSpec: String): Int { + return specs.indexOf(tileSpec) + } + + override fun getNewInstanceId(): InstanceId { + return qsTileHost.newInstanceId + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 8bbdeeda356c..0ca897373d13 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -37,8 +37,9 @@ import com.android.systemui.ProtoDumpable; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.nano.SystemUIProtoDump; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -48,9 +49,10 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.nano.QsTileState; +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.settings.UserFileManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.AutoTileManager; @@ -85,7 +87,8 @@ import javax.inject.Provider; * This class also provides the interface for adding/removing/changing tiles. */ @SysUISingleton -public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable { +public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable, + PanelInteractor, CustomTileAddedRepository { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int MAX_QS_INSTANCE_ID = 1 << 20; @@ -99,7 +102,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P private final ArrayList<String> mTileSpecs = new ArrayList<>(); private final TunerService mTunerService; private final PluginManager mPluginManager; - private final DumpManager mDumpManager; private final QSLogger mQSLogger; private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; @@ -122,9 +124,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged private boolean mTilesListDirty = true; - private final TileServiceRequestController mTileServiceRequestController; private TileLifecycleManager.Factory mTileLifeCycleManagerFactory; + private final FeatureFlags mFeatureFlags; + @Inject public QSTileHost(Context context, QSFactory defaultFactory, @@ -132,35 +135,32 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, - DumpManager dumpManager, Optional<CentralSurfaces> centralSurfacesOptional, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, - TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager + UserFileManager userFileManager, + FeatureFlags featureFlags ) { mContext = context; mUserContext = context; mTunerService = tunerService; mPluginManager = pluginManager; - mDumpManager = dumpManager; mQSLogger = qsLogger; mUiEventLogger = uiEventLogger; mMainExecutor = mainExecutor; - mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this); mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; mUserFileManager = userFileManager; + mFeatureFlags = featureFlags; mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID); mCentralSurfacesOptional = centralSurfacesOptional; mQsFactories.add(defaultFactory); pluginManager.addPluginListener(this, QSFactory.class, true); - mDumpManager.registerDumpable(TAG, this); mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; @@ -172,7 +172,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P tunerService.addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = autoTiles.get(); - mTileServiceRequestController.init(); }); } @@ -186,8 +185,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mAutoTiles.destroy(); mTunerService.removeTunable(this); mPluginManager.removePluginListener(this); - mDumpManager.unregisterDumpable(TAG); - mTileServiceRequestController.destroy(); } @Override @@ -300,6 +297,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P if (!TILES_SETTING.equals(key)) { return; } + // Do not process tiles if the flag is enabled. + if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { + return; + } if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt index 964fe7104324..3ddd9f1cfe5f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.dagger import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QSHostAdapter import com.android.systemui.qs.QSTileHost import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository @@ -31,7 +32,7 @@ import dagger.Provides @Module interface QSHostModule { - @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost + @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost @Module companion object { @@ -39,7 +40,7 @@ interface QSHostModule { @JvmStatic fun providePanelInteractor( featureFlags: FeatureFlags, - qsHost: QSHost, + qsHost: QSTileHost, panelInteractorImpl: PanelInteractorImpl ): PanelInteractor { return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { @@ -53,7 +54,7 @@ interface QSHostModule { @JvmStatic fun provideCustomTileAddedRepository( featureFlags: FeatureFlags, - qsHost: QSHost, + qsHost: QSTileHost, customTileAddedRepository: CustomTileAddedSharedPrefsRepository ): CustomTileAddedRepository { return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index 00f0a67dbe22..e212bc4e7f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.log.LogBufferFactory import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger import dagger.Binds @@ -38,6 +40,11 @@ abstract class QSPipelineModule { abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository @Binds + abstract fun bindCurrentTilesInteractor( + impl: CurrentTilesInteractorImpl + ): CurrentTilesInteractor + + @Binds @IntoMap @ClassKey(PrototypeCoreStartable::class) abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt index d254e1b3d0d7..595b29a9dcb8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt @@ -32,6 +32,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -53,6 +54,8 @@ interface TileSpecRepository { * at the end of the list. * * Passing [TileSpec.Invalid] is a noop. + * + * Trying to add a tile beyond the end of the list will add it at the end. */ suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END) @@ -61,7 +64,7 @@ interface TileSpecRepository { * * Passing [TileSpec.Invalid] or a non present tile is a noop. */ - suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec) + suspend fun removeTiles(@UserIdInt userId: Int, tiles: Collection<TileSpec>) /** * Sets the list of current [tiles] for a given [userId]. @@ -106,6 +109,7 @@ constructor( } .onStart { emit(Unit) } .map { secureSettings.getStringForUser(SETTING, userId) ?: "" } + .distinctUntilChanged() .onEach { logger.logTilesChangedInSettings(it, userId) } .map { parseTileSpecs(it, userId) } .flowOn(backgroundDispatcher) @@ -117,7 +121,7 @@ constructor( } val tilesList = loadTiles(userId).toMutableList() if (tile !in tilesList) { - if (position < 0) { + if (position < 0 || position >= tilesList.size) { tilesList.add(tile) } else { tilesList.add(position, tile) @@ -126,12 +130,12 @@ constructor( } } - override suspend fun removeTile(userId: Int, tile: TileSpec) { - if (tile == TileSpec.Invalid) { + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { + if (tiles.all { it == TileSpec.Invalid }) { return } val tilesList = loadTiles(userId).toMutableList() - if (tilesList.remove(tile)) { + if (tilesList.removeAll(tiles)) { storeTiles(userId, tilesList.toList()) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt new file mode 100644 index 000000000000..91c6e8b5fcb6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import com.android.systemui.Dumpable +import com.android.systemui.ProtoDumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.nano.SystemUIProtoDump +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileLifecycleManager +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.domain.model.TileModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.toProto +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.pairwise +import java.io.PrintWriter +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * Interactor for retrieving the list of current QS tiles, as well as making changes to this list + * + * It is [ProtoDumpable] as it needs to be able to dump state for CTS tests. + */ +interface CurrentTilesInteractor : ProtoDumpable { + /** Current list of tiles with their corresponding spec. */ + val currentTiles: StateFlow<List<TileModel>> + + /** User for the [currentTiles]. */ + val userId: StateFlow<Int> + + /** [Context] corresponding to [userId] */ + val userContext: StateFlow<Context> + + /** List of specs corresponding to the last value of [currentTiles] */ + val currentTilesSpecs: List<TileSpec> + get() = currentTiles.value.map(TileModel::spec) + + /** List of tiles corresponding to the last value of [currentTiles] */ + val currentQSTiles: List<QSTile> + get() = currentTiles.value.map(TileModel::tile) + + /** + * Requests that a tile be added in the list of tiles for the current user. + * + * @see TileSpecRepository.addTile + */ + fun addTile(spec: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END) + + /** + * Requests that tiles be removed from the list of tiles for the current user + * + * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and + * marked as removed. + * + * @see TileSpecRepository.removeTiles + */ + fun removeTiles(specs: Collection<TileSpec>) + + /** + * Requests that the list of tiles for the current user is changed to [specs]. + * + * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and + * marked as removed. + * + * @see TileSpecRepository.setTiles + */ + fun setTiles(specs: List<TileSpec>) +} + +/** + * This implementation of [CurrentTilesInteractor] will try to re-use existing [QSTile] objects when + * possible, in particular: + * * It will only destroy tiles when they are not part of the list of tiles anymore + * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch] + * * [CustomTile]s will only be destroyed if the user changes. + */ +@SysUISingleton +class CurrentTilesInteractorImpl +@Inject +constructor( + private val tileSpecRepository: TileSpecRepository, + private val userRepository: UserRepository, + private val customTileStatePersister: CustomTileStatePersister, + private val tileFactory: QSFactory, + private val customTileAddedRepository: CustomTileAddedRepository, + private val tileLifecycleManagerFactory: TileLifecycleManager.Factory, + private val userTracker: UserTracker, + @Main private val mainDispatcher: CoroutineDispatcher, + @Background private val backgroundDispatcher: CoroutineDispatcher, + @Application private val scope: CoroutineScope, + private val logger: QSPipelineLogger, + featureFlags: FeatureFlags, +) : CurrentTilesInteractor { + + private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> = + MutableStateFlow(emptyList()) + + override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow() + + // This variable should only be accessed inside the collect of `startTileCollection`. + private val specsToTiles = mutableMapOf<TileSpec, QSTile>() + + private val currentUser = MutableStateFlow(userTracker.userId) + override val userId = currentUser.asStateFlow() + + private val _userContext = MutableStateFlow(userTracker.userContext) + override val userContext = _userContext.asStateFlow() + + init { + if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { + startTileCollection() + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun startTileCollection() { + scope.launch { + userRepository.selectedUserInfo + .flatMapLatest { user -> + currentUser.value = user.id + _userContext.value = userTracker.userContext + tileSpecRepository.tilesSpecs(user.id).map { user.id to it } + } + .distinctUntilChanged() + .pairwise(-1 to emptyList()) + .flowOn(backgroundDispatcher) + .collect { (old, new) -> + val newTileList = new.second + val userChanged = old.first != new.first + val newUser = new.first + + // Destroy all tiles that are not in the new set + specsToTiles + .filter { it.key !in newTileList } + .forEach { entry -> + logger.logTileDestroyed( + entry.key, + if (userChanged) { + QSPipelineLogger.TileDestroyedReason + .TILE_NOT_PRESENT_IN_NEW_USER + } else { + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + } + ) + entry.value.destroy() + } + // MutableMap will keep the insertion order + val newTileMap = mutableMapOf<TileSpec, QSTile>() + + newTileList.forEach { tileSpec -> + if (tileSpec !in newTileMap) { + val newTile = + if (tileSpec in specsToTiles) { + processExistingTile( + tileSpec, + specsToTiles.getValue(tileSpec), + userChanged, + newUser + ) + ?: createTile(tileSpec) + } else { + createTile(tileSpec) + } + if (newTile != null) { + newTileMap[tileSpec] = newTile + } + } + } + + val resolvedSpecs = newTileMap.keys.toList() + specsToTiles.clear() + specsToTiles.putAll(newTileMap) + _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) } + if (resolvedSpecs != newTileList) { + // There were some tiles that couldn't be created. Change the value in the + // repository + launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) } + } + } + } + } + + override fun addTile(spec: TileSpec, position: Int) { + scope.launch { + tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position) + } + } + + override fun removeTiles(specs: Collection<TileSpec>) { + val currentSpecsCopy = currentTilesSpecs.toSet() + val user = currentUser.value + // intersect: tiles that are there and are being removed + val toFree = currentSpecsCopy.intersect(specs).filterIsInstance<TileSpec.CustomTileSpec>() + toFree.forEach { onCustomTileRemoved(it.componentName, user) } + if (currentSpecsCopy.intersect(specs).isNotEmpty()) { + // We don't want to do the call to set in case getCurrentTileSpecs is not the most + // up to date for this user. + scope.launch { tileSpecRepository.removeTiles(user, specs) } + } + } + + override fun setTiles(specs: List<TileSpec>) { + val currentSpecsCopy = currentTilesSpecs + val user = currentUser.value + if (currentSpecsCopy != specs) { + // minus: tiles that were there but are not there anymore + val toFree = currentSpecsCopy.minus(specs).filterIsInstance<TileSpec.CustomTileSpec>() + toFree.forEach { onCustomTileRemoved(it.componentName, user) } + scope.launch { tileSpecRepository.setTiles(user, specs) } + } + } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("CurrentTileInteractorImpl:") + pw.println("User: ${userId.value}") + currentTiles.value + .map { it.tile } + .filterIsInstance<Dumpable>() + .forEach { it.dump(pw, args) } + } + + override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) { + val data = + currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray() + systemUIProtoDump.tiles = data + } + + private fun onCustomTileRemoved(componentName: ComponentName, userId: Int) { + val intent = Intent().setComponent(componentName) + val lifecycleManager = tileLifecycleManagerFactory.create(intent, UserHandle.of(userId)) + lifecycleManager.onStopListening() + lifecycleManager.onTileRemoved() + customTileStatePersister.removeState(TileServiceKey(componentName, userId)) + customTileAddedRepository.setTileAdded(componentName, userId, false) + lifecycleManager.flushMessagesAndUnbind() + } + + private suspend fun createTile(spec: TileSpec): QSTile? { + val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) } + if (tile == null) { + logger.logTileNotFoundInFactory(spec) + return null + } else { + tile.tileSpec = spec.spec + return if (!tile.isAvailable) { + logger.logTileDestroyed( + spec, + QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE, + ) + tile.destroy() + null + } else { + logger.logTileCreated(spec) + tile + } + } + } + + private fun processExistingTile( + tileSpec: TileSpec, + qsTile: QSTile, + userChanged: Boolean, + user: Int, + ): QSTile? { + return when { + !qsTile.isAvailable -> { + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + ) + qsTile.destroy() + null + } + // Tile is in the current list of tiles and available. + // We have a handful of different cases + qsTile !is CustomTile -> { + // The tile is not a custom tile. Make sure they are reset to the correct user + qsTile.removeCallbacks() + if (userChanged) { + qsTile.userSwitch(user) + logger.logTileUserChanged(tileSpec, user) + } + qsTile + } + qsTile.user == user -> { + // The tile is a custom tile for the same user, just return it + qsTile.removeCallbacks() + qsTile + } + else -> { + // The tile is a custom tile and the user has changed. Destroy it + qsTile.destroy() + logger.logTileDestroyed( + tileSpec, + QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED + ) + null + } + } + } +} diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt index a2d65e4e7119..e2381ecd8b97 100644 --- a/keystore/java/android/security/GenerateRkpKeyException.java +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt @@ -1,11 +1,11 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,18 +14,19 @@ * limitations under the License. */ -package android.security; +package com.android.systemui.qs.pipeline.domain.model + +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec /** - * Thrown on problems in attempting to attest to a key using a remotely provisioned key. - * - * @hide + * Container for a [tile] and its [spec]. The following must be true: + * ``` + * spec.spec == tile.tileSpec + * ``` */ -public class GenerateRkpKeyException extends Exception { - - /** - * Constructs a new {@code GenerateRkpKeyException}. - */ - public GenerateRkpKeyException() { +data class TileModel(val spec: TileSpec, val tile: QSTile) { + init { + check(spec.spec == tile.tileSpec) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt index 69d8248a11f5..89408006c300 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt @@ -93,7 +93,7 @@ constructor( private fun performRemove(args: List<String>, spec: TileSpec) { val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id - scope.launch { tileSpecRepository.removeTile(user, spec) } + scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) } } override fun help(pw: PrintWriter) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt index c691c2f668ad..af1cd0995a21 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt @@ -66,6 +66,10 @@ sealed class TileSpec private constructor(open val spec: String) { } } + fun create(component: ComponentName): CustomTileSpec { + return CustomTileSpec(CustomTile.toSpec(component), component) + } + private val String.isCustomTileSpec: Boolean get() = startsWith(CustomTile.PREFIX) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index 200f7431e906..767ce919d027 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -73,4 +73,59 @@ constructor( { "Tiles changed in settings for user $int1: $str1" } ) } + + /** Log when a tile is destroyed and its reason for destroying. */ + fun logTileDestroyed(spec: TileSpec, reason: TileDestroyedReason) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { + str1 = spec.toString() + str2 = reason.readable + }, + { "Tile $str1 destroyed. Reason: $str2" } + ) + } + + /** Log when a tile is created. */ + fun logTileCreated(spec: TileSpec) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.DEBUG, + { str1 = spec.toString() }, + { "Tile $str1 created" } + ) + } + + /** Ĺog when trying to create a tile, but it's not found in the factory. */ + fun logTileNotFoundInFactory(spec: TileSpec) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.VERBOSE, + { str1 = spec.toString() }, + { "Tile $str1 not found in factory" } + ) + } + + /** Log when the user is changed for a platform tile. */ + fun logTileUserChanged(spec: TileSpec, user: Int) { + tileListLogBuffer.log( + TILE_LIST_TAG, + LogLevel.VERBOSE, + { + str1 = spec.toString() + int1 = user + }, + { "User changed to $int1 for tile $str1" } + ) + } + + /** Reasons for destroying an existing tile. */ + enum class TileDestroyedReason(val readable: String) { + TILE_REMOVED("Tile removed from current set"), + CUSTOM_TILE_USER_CHANGED("User changed for custom tile"), + NEW_TILE_NOT_AVAILABLE("New tile not available"), + EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"), + TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"), + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index f62e93976d71..cffe45fadaa3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -16,6 +16,7 @@ package com.android.systemui.recents; +import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; @@ -32,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; @@ -113,6 +115,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.function.Supplier; @@ -390,6 +393,16 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + StringBuilder extraComponentList = new StringBuilder(" components: "); + if (intent.hasExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST)) { + String[] comps = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (comps != null) { + for (String c : comps) { + extraComponentList.append(c).append(", "); + } + } + } + Log.d(TAG_OPS, "launcherStateChanged intent: " + intent + extraComponentList); updateEnabledState(); // Reconnect immediately, instead of waiting for resume to arrive. @@ -400,9 +413,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - if (SysUiState.DEBUG) { - Log.d(TAG_OPS, "Overview proxy service connected"); - } + Log.d(TAG_OPS, "Overview proxy service connected"); mConnectionBackoffAttempts = 0; mHandler.removeCallbacks(mDeferredConnectionCallback); try { @@ -447,6 +458,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis notifySystemUiStateFlags(mSysUiState.getFlags()); notifyConnectionChanged(); + if (mLatchForOnUserChanging != null) { + mLatchForOnUserChanging.countDown(); + mLatchForOnUserChanging = null; + } } @Override @@ -501,11 +516,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } }; + private CountDownLatch mLatchForOnUserChanging; private final UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() { @Override - public void onUserChanged(int newUser, @NonNull Context userContext) { + public void onUserChanging(int newUser, @NonNull Context userContext, + CountDownLatch latch) { mConnectionBackoffAttempts = 0; + mLatchForOnUserChanging = latch; internalConnectToCurrentUser("User changed"); } }; @@ -676,11 +694,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded, - boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) { + boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing, + boolean panelExpanded, boolean isDreaming) { mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING, keyguardShowing && !keyguardOccluded) .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED, keyguardShowing && keyguardOccluded) + .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, + keyguardGoingAway) .setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing) .setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing) .setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 6f85c45a6614..c9d1da38b196 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -289,7 +289,7 @@ public class ScreenshotController { if (DEBUG_INPUT) { Log.d(TAG, "Predictive Back callback dispatched"); } - respondToBack(); + respondToKeyDismissal(); }; private ScreenshotView mScreenshotView; @@ -581,7 +581,7 @@ public class ScreenshotController { } } - private void respondToBack() { + private void respondToKeyDismissal() { dismissScreenshot(SCREENSHOT_DISMISSED_OTHER); } @@ -641,11 +641,11 @@ public class ScreenshotController { mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setOnKeyListener((v, keyCode, event) -> { - if (keyCode == KeyEvent.KEYCODE_BACK) { + if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { if (DEBUG_INPUT) { - Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK"); + Log.d(TAG, "onKeyEvent: " + keyCode); } - respondToBack(); + respondToKeyDismissal(); return true; } return false; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java index d0b7ad3e9dd5..394949297d6d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java @@ -27,9 +27,15 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.IBinder; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.ServiceConnector; import com.android.internal.statusbar.IAppClipsService; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Application; @@ -37,6 +43,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.wm.shell.bubbles.Bubbles; import java.util.Optional; +import java.util.concurrent.ExecutionException; import javax.inject.Inject; @@ -46,21 +53,63 @@ import javax.inject.Inject; */ public class AppClipsService extends Service { + private static final String TAG = AppClipsService.class.getSimpleName(); + @Application private final Context mContext; private final FeatureFlags mFeatureFlags; private final Optional<Bubbles> mOptionalBubbles; private final DevicePolicyManager mDevicePolicyManager; + private final UserManager mUserManager; + private final boolean mAreTaskAndTimeIndependentPrerequisitesMet; + @VisibleForTesting() + @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile; + @Inject public AppClipsService(@Application Context context, FeatureFlags featureFlags, - Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) { + Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager, + UserManager userManager) { mContext = context; mFeatureFlags = featureFlags; mOptionalBubbles = optionalBubbles; mDevicePolicyManager = devicePolicyManager; + mUserManager = userManager; + + // The consumer of this service are apps that call through StatusBarManager API to query if + // it can use app clips API. Since these apps can be launched as work profile users, this + // service will start as work profile user. SysUI doesn't share injected instances for + // different users. This is why the bubbles instance injected will be incorrect. As the apps + // don't generally have permission to connect to a service running as different user, we + // start a proxy connection to communicate with the main user's version of this service. + if (mUserManager.isManagedProfile()) { + // No need to check for prerequisites in this case as those are incorrect for work + // profile user instance of the service and the main user version of the service will + // take care of this check. + mAreTaskAndTimeIndependentPrerequisitesMet = false; + + // Get the main user so that we can connect to the main user's version of the service. + UserHandle mainUser = mUserManager.getMainUser(); + if (mainUser == null) { + // If main user is not available there isn't much we can do, no apps can use app + // clips. + return; + } + + // Set up the connection to be used later during onBind callback. + mProxyConnectorToMainProfile = + new ServiceConnector.Impl<>( + context, + new Intent(context, AppClipsService.class), + Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY + | Context.BIND_NOT_VISIBLE, + mainUser.getIdentifier(), + IAppClipsService.Stub::asInterface); + return; + } mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables(); + mProxyConnectorToMainProfile = null; } private boolean checkIndependentVariables() { @@ -95,6 +144,13 @@ public class AppClipsService extends Service { return new IAppClipsService.Stub() { @Override public boolean canLaunchCaptureContentActivityForNote(int taskId) { + // In case of managed profile, use the main user's instance of the service. Callers + // cannot directly connect to the main user's instance as they may not have the + // permission to interact across users. + if (mUserManager.isManagedProfile()) { + return canLaunchCaptureContentActivityForNoteFromMainUser(taskId); + } + if (!mAreTaskAndTimeIndependentPrerequisitesMet) { return false; } @@ -107,4 +163,21 @@ public class AppClipsService extends Service { } }; } + + /** Returns whether the app clips API can be used by querying the service as the main user. */ + private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) { + if (mProxyConnectorToMainProfile == null) { + return false; + } + + try { + AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult( + service -> service.canLaunchCaptureContentActivityForNote(taskId)); + return future.get(); + } catch (ExecutionException | InterruptedException e) { + Log.d(TAG, "Exception from service\n" + e); + } + + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java index 3cb1a34a921c..0487cbc995dd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java @@ -40,6 +40,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.ResultReceiver; +import android.os.UserHandle; +import android.os.UserManager; import android.util.Log; import androidx.annotation.Nullable; @@ -79,13 +81,10 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; + static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; + static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; + static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0); private final DevicePolicyManager mDevicePolicyManager; @@ -95,6 +94,7 @@ public class AppClipsTrampolineActivity extends Activity { private final PackageManager mPackageManager; private final UserTracker mUserTracker; private final UiEventLogger mUiEventLogger; + private final UserManager mUserManager; private final ResultReceiver mResultReceiver; private Intent mKillAppClipsBroadcastIntent; @@ -103,7 +103,7 @@ public class AppClipsTrampolineActivity extends Activity { public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController, PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, - @Main Handler mainHandler) { + UserManager userManager, @Main Handler mainHandler) { mDevicePolicyManager = devicePolicyManager; mFeatureFlags = flags; mOptionalBubbles = optionalBubbles; @@ -111,6 +111,7 @@ public class AppClipsTrampolineActivity extends Activity { mPackageManager = packageManager; mUserTracker = userTracker; mUiEventLogger = uiEventLogger; + mUserManager = userManager; mResultReceiver = createResultReceiver(mainHandler); } @@ -123,6 +124,12 @@ public class AppClipsTrampolineActivity extends Activity { return; } + if (mUserManager.isManagedProfile()) { + maybeStartActivityForWPUser(); + finish(); + return; + } + if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) { finish(); return; @@ -191,6 +198,19 @@ public class AppClipsTrampolineActivity extends Activity { } } + private void maybeStartActivityForWPUser() { + UserHandle mainUser = mUserManager.getMainUser(); + if (mainUser == null) { + setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED); + return; + } + + // Start the activity as the main user with activity result forwarding. + startActivityAsUser( + new Intent(this, AppClipsTrampolineActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser); + } + private void setErrorResultAndFinish(int errorCode) { setResult(RESULT_OK, new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode)); diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 72286f175671..3711a2f39b7b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -162,7 +162,7 @@ open class UserTrackerImpl internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { override fun onBeforeUserSwitching(newUserId: Int) { - setUserIdInternal(newUserId) + handleBeforeUserSwitching(newUserId) } override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { @@ -180,6 +180,10 @@ open class UserTrackerImpl internal constructor( }, TAG) } + protected open fun handleBeforeUserSwitching(newUserId: Int) { + setUserIdInternal(newUserId) + } + @WorkerThread protected open fun handleUserSwitching(newUserId: Int) { Assert.isNotMainThread() diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt index 754036d3baa9..b8bd95c89ec8 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt @@ -14,9 +14,9 @@ package com.android.systemui.shade import android.view.MotionEvent +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer import java.text.SimpleDateFormat import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 79d3b26e01c7..aedd9762a601 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -22,10 +22,6 @@ import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; -import static androidx.constraintlayout.widget.ConstraintSet.END; -import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; - -import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; @@ -73,12 +69,6 @@ import android.os.Trace; import android.os.UserManager; import android.os.VibrationEffect; import android.provider.Settings; -import android.transition.ChangeBounds; -import android.transition.Transition; -import android.transition.TransitionListenerAdapter; -import android.transition.TransitionManager; -import android.transition.TransitionSet; -import android.transition.TransitionValues; import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; @@ -100,8 +90,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import androidx.constraintlayout.widget.ConstraintSet; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; @@ -162,7 +150,7 @@ import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.plugins.ClockController; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; @@ -299,11 +287,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); private static final Rect EMPTY_RECT = new Rect(); /** - * Duration to use for the animator when the keyguard status view alignment changes, and a - * custom clock animation is in use. - */ - private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; - /** * Whether the Shade should animate to reflect Back gesture progress. * To minimize latency at runtime, we cache this, else we'd be reading it every time * updateQsExpansion() is called... and it's called very often. @@ -550,8 +533,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final KeyguardMediaController mKeyguardMediaController; - private boolean mStatusViewCentered = true; - private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition; private final Optional<NotificationPanelUnfoldAnimationController> mNotificationPanelUnfoldAnimationController; @@ -682,35 +663,29 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump step.getTransitionState() == TransitionState.RUNNING; }; - private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = - new TransitionListenerAdapter() { - @Override - public void onTransitionCancel(Transition transition) { - mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - } - - @Override - public void onTransitionEnd(Transition transition) { - mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - } - }; + private final ActivityStarter mActivityStarter; @Inject public NotificationPanelViewController(NotificationPanelView view, @Main Handler handler, LayoutInflater layoutInflater, FeatureFlags featureFlags, - NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler, + NotificationWakeUpCoordinator coordinator, + PulseExpansionHandler pulseExpansionHandler, DynamicPrivacyController dynamicPrivacyController, - KeyguardBypassController bypassController, FalsingManager falsingManager, + KeyguardBypassController bypassController, + FalsingManager falsingManager, FalsingCollector falsingCollector, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarWindowStateController statusBarWindowStateController, NotificationShadeWindowController notificationShadeWindowController, DozeLog dozeLog, - DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper, - LatencyTracker latencyTracker, PowerManager powerManager, + DozeParameters dozeParameters, + CommandQueue commandQueue, + VibratorHelper vibratorHelper, + LatencyTracker latencyTracker, + PowerManager powerManager, AccessibilityManager accessibilityManager, @DisplayId int displayId, KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger, @@ -771,7 +746,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump Provider<MultiShadeInteractor> multiShadeInteractorProvider, DumpManager dumpManager, KeyguardLongPressViewModel keyguardLongPressViewModel, - KeyguardInteractor keyguardInteractor) { + KeyguardInteractor keyguardInteractor, + ActivityStarter activityStarter) { mInteractionJankMonitor = interactionJankMonitor; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override @@ -952,6 +928,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return Unit.INSTANCE; }, mFalsingManager); + mActivityStarter = activityStarter; onFinishInflate(); keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @@ -1313,9 +1290,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate( R.layout.keyguard_status_view, mNotificationContainerParent, false); mNotificationContainerParent.addView(keyguardStatusView, statusIndex); - // When it's reinflated, this is centered by default. If it shouldn't be, this will update - // below when resources are updated. - mStatusViewCentered = true; attachSplitShadeMediaPlayerContainer( keyguardStatusView.findViewById(R.id.status_view_media_container)); @@ -1394,7 +1368,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mLockIconViewController, stringResourceId -> mKeyguardIndicationController.showTransientIndication(stringResourceId), - mVibratorHelper); + mVibratorHelper, + mActivityStarter); } @VisibleForTesting @@ -1609,68 +1584,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void updateKeyguardStatusViewAlignment(boolean animate) { boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); - if (mStatusViewCentered != shouldBeCentered) { - mStatusViewCentered = shouldBeCentered; - ConstraintSet constraintSet = new ConstraintSet(); - constraintSet.clone(mNotificationContainerParent); - int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline; - constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END); - if (animate) { - mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); - ChangeBounds transition = new ChangeBounds(); - if (mSplitShadeEnabled) { - // Excluding media from the transition on split-shade, as it doesn't transition - // horizontally properly. - transition.excludeTarget(R.id.status_view_media_container, true); - } - - transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - - ClockController clock = mKeyguardStatusViewController.getClockController(); - boolean customClockAnimation = clock != null - && clock.getConfig().getHasCustomPositionUpdatedAnimation(); - - if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { - // Find the clock, so we can exclude it from this transition. - FrameLayout clockContainerView = - mView.findViewById(R.id.lockscreen_clock_view_large); - - // The clock container can sometimes be null. If it is, just fall back to the - // old animation rather than setting up the custom animations. - if (clockContainerView == null || clockContainerView.getChildCount() == 0) { - transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition( - mNotificationContainerParent, transition); - } else { - View clockView = clockContainerView.getChildAt(0); - - transition.excludeTarget(clockView, /* exclude= */ true); - - TransitionSet set = new TransitionSet(); - set.addTransition(transition); - - SplitShadeTransitionAdapter adapter = - new SplitShadeTransitionAdapter(mKeyguardStatusViewController); - - // Use linear here, so the actual clock can pick its own interpolator. - adapter.setInterpolator(Interpolators.LINEAR); - adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION); - adapter.addTarget(clockView); - set.addTransition(adapter); - set.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition(mNotificationContainerParent, set); - } - } else { - transition.addListener(mKeyguardStatusAlignmentTransitionListener); - TransitionManager.beginDelayedTransition( - mNotificationContainerParent, transition); - } - } - - constraintSet.applyTo(mNotificationContainerParent); - } - mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered)); + mKeyguardStatusViewController.updateAlignment( + mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate); + mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); } private boolean shouldKeyguardStatusViewBeCentered() { @@ -3324,7 +3240,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation); ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection); ipw.print("mMinFraction="); ipw.println(mMinFraction); - ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered); ipw.print("mSplitShadeFullTransitionDistance="); ipw.println(mSplitShadeFullTransitionDistance); ipw.print("mSplitShadeScrimTransitionDistance="); @@ -4925,6 +4840,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } handled |= handleTouch(event); + mShadeLog.logOnTouchEventLastReturn(event, !mDozing, handled); return !mDozing || handled; } @@ -5107,6 +5023,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } break; } + mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking); return !mGestureWaitForTouchSlop || mTracking; } @@ -5117,65 +5034,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - static class SplitShadeTransitionAdapter extends Transition { - private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; - private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; - - private final KeyguardStatusViewController mController; - - SplitShadeTransitionAdapter(KeyguardStatusViewController controller) { - mController = controller; - } - - private void captureValues(TransitionValues transitionValues) { - Rect boundsRect = new Rect(); - boundsRect.left = transitionValues.view.getLeft(); - boundsRect.top = transitionValues.view.getTop(); - boundsRect.right = transitionValues.view.getRight(); - boundsRect.bottom = transitionValues.view.getBottom(); - transitionValues.values.put(PROP_BOUNDS, boundsRect); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Nullable - @Override - public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues, - @Nullable TransitionValues endValues) { - if (startValues == null || endValues == null) { - return null; - } - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - - Rect from = (Rect) startValues.values.get(PROP_BOUNDS); - Rect to = (Rect) endValues.values.get(PROP_BOUNDS); - - anim.addUpdateListener(animation -> { - ClockController clock = mController.getClockController(); - if (clock == null) { - return; - } - - clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction()); - }); - - return anim; - } - - @Override - public String[] getTransitionProperties() { - return TRANSITION_PROPERTIES; - } - } - private final class HeadsUpNotificationViewControllerImpl implements HeadsUpTouchHelper.HeadsUpNotificationViewController { @Override diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 156e4fd1889f..af74c2793cc9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -561,6 +561,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW for (StatusBarWindowCallback cb : activeCallbacks) { cb.onStateChanged(mCurrentState.keyguardShowing, mCurrentState.keyguardOccluded, + mCurrentState.keyguardGoingAway, mCurrentState.bouncerShowing, mCurrentState.dozing, mCurrentState.panelExpanded, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt index fed9b8469c4b..7812f07fc59c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt @@ -16,9 +16,9 @@ package com.android.systemui.shade +import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dump.DumpsysTableLogger import com.android.systemui.dump.Row -import com.android.systemui.plugins.util.RingBuffer import com.android.systemui.shade.NotificationShadeWindowState.Buffer import com.android.systemui.statusbar.StatusBarState diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index b31ec3319781..7cb1cbe77539 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -17,6 +17,8 @@ package com.android.systemui.shade; +import static android.view.WindowInsets.Type.ime; + import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS; @@ -463,9 +465,17 @@ public class QuickSettingsController { return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight; } + private boolean isRemoteInputActiveWithKeyboardUp() { + //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed. + // The check for keyboard visibility is a temporary workaround that allows QS to expand + // even when isRemoteInputActive is mistakenly returning true. + return mRemoteInputManager.isRemoteInputActive() + && mPanelView.getRootWindowInsets().isVisible(ime()); + } + public boolean isExpansionEnabled() { return mExpansionEnabledPolicy && mExpansionEnabledAmbient - && !mRemoteInputManager.isRemoteInputActive(); + && !isRemoteInputActiveWithKeyboardUp(); } public float getTransitioningToFullShadeProgress() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index b79f32a6eae1..b4653bef766d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -43,13 +43,13 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController -import com.android.systemui.qs.carrier.QSCarrierGroup -import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT +import com.android.systemui.shade.carrier.ShadeCarrierGroup +import com.android.systemui.shade.carrier.ShadeCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusBarLocation @@ -87,7 +87,7 @@ constructor( private val variableDateViewControllerFactory: VariableDateViewController.Factory, @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController, private val dumpManager: DumpManager, - private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, + private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder, private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager, private val demoModeController: DemoModeController, private val qsBatteryModeController: QsBatteryModeController, @@ -114,13 +114,13 @@ constructor( private lateinit var iconManager: StatusBarIconController.TintedIconManager private lateinit var carrierIconSlots: List<String> - private lateinit var qsCarrierGroupController: QSCarrierGroupController + private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon) private val clock: Clock = header.findViewById(R.id.clock) private val date: TextView = header.findViewById(R.id.date) private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons) - private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group) + private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group) private var roundedCorners = 0 private var cutout: DisplayCutout? = null @@ -243,7 +243,7 @@ constructor( override fun onDensityOrFontScaleChanged() { clock.setTextAppearance(R.style.TextAppearance_QS_Status) date.setTextAppearance(R.style.TextAppearance_QS_Status) - qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) + mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers) loadConstraints() header.minHeight = resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height) @@ -266,8 +266,8 @@ constructor( carrierIconSlots = listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile)) - qsCarrierGroupController = - qsCarrierGroupControllerBuilder.setQSCarrierGroup(qsCarrierGroup).build() + mShadeCarrierGroupController = + shadeCarrierGroupControllerBuilder.setShadeCarrierGroup(mShadeCarrierGroup).build() privacyIconsController.onParentVisible() } @@ -284,7 +284,7 @@ constructor( v.pivotX = newPivot v.pivotY = v.height.toFloat() / 2 - qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0) + mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0) } dumpManager.registerDumpable(this) @@ -439,12 +439,14 @@ constructor( } private fun updateListeners() { - qsCarrierGroupController.setListening(visible) + mShadeCarrierGroupController.setListening(visible) if (visible) { - updateSingleCarrier(qsCarrierGroupController.isSingleCarrier) - qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) } + updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier) + mShadeCarrierGroupController.setOnSingleCarrierChangedListener { + updateSingleCarrier(it) + } } else { - qsCarrierGroupController.setOnSingleCarrierChangedListener(null) + mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index da4944c20f6e..a93183865a3f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -316,4 +316,80 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { { "QSC NotificationsClippingTopBound set to $int1 - $int2" } ) } + + fun logOnTouchEventLastReturn( + event: MotionEvent, + dozing: Boolean, + handled: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = dozing + bool2 = handled + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, + { + "NPVC onTouchEvent last return: !mDozing: $bool1 || handled: $bool2 " + + "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + } + ) + } + + fun logHandleTouchLastReturn( + event: MotionEvent, + gestureWaitForTouchSlop: Boolean, + tracking: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = gestureWaitForTouchSlop + bool2 = tracking + long1 = event.eventTime + long2 = event.downTime + int1 = event.action + int2 = event.classification + double1 = event.y.toDouble() + }, + { + "NPVC handleTouch last return: !mGestureWaitForTouchSlop: $bool1 " + + "|| mTracking: $bool2 " + + "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + } + ) + } + + fun logUpdateNotificationPanelTouchState( + disabled: Boolean, + isGoingToSleep: Boolean, + shouldControlScreenOff: Boolean, + deviceInteractive: Boolean, + isPulsing: Boolean, + isFrpActive: Boolean, + ) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = disabled + bool2 = isGoingToSleep + bool3 = shouldControlScreenOff + bool4 = deviceInteractive + str1 = isPulsing.toString() + str2 = isFrpActive.toString() + }, + { + "CentralSurfaces updateNotificationPanelTouchState set disabled to: $bool1\n" + + "isGoingToSleep: $bool2, !shouldControlScreenOff: $bool3," + + "!mDeviceInteractive: $bool4, !isPulsing: $str1, isFrpActive: $str2" + } + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt index e925b5472c27..958230bbef99 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier +package com.android.systemui.shade.carrier /** * Represents the state of cell signal for a particular slot. * - * To be used between [QSCarrierGroupController] and [QSCarrier]. + * To be used between [ShadeCarrierGroupController] and [ShadeCarrier]. */ data class CellSignalState( @JvmField val visible: Boolean = false, @@ -37,7 +37,6 @@ data class CellSignalState( * @return `this` if `this.visible == visible`. Else, a new copy with the visibility changed. */ fun changeVisibility(visible: Boolean): CellSignalState { - if (this.visible == visible) return this - else return copy(visible = visible) + if (this.visible == visible) return this else return copy(visible = visible) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java index b5ceeaed4904..8586828af0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier; +package com.android.systemui.shade.carrier; import android.annotation.StyleRes; import android.content.Context; @@ -38,7 +38,7 @@ import com.android.systemui.util.LargeScreenUtils; import java.util.Objects; -public class QSCarrier extends LinearLayout { +public class ShadeCarrier extends LinearLayout { private View mMobileGroup; private TextView mCarrierText; @@ -50,19 +50,19 @@ public class QSCarrier extends LinearLayout { private boolean mMobileSignalInitialized = false; private boolean mIsSingleCarrier; - public QSCarrier(Context context) { + public ShadeCarrier(Context context) { super(context); } - public QSCarrier(Context context, AttributeSet attrs) { + public ShadeCarrier(Context context, AttributeSet attrs) { super(context, attrs); } - public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr) { + public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } - public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -72,7 +72,7 @@ public class QSCarrier extends LinearLayout { mMobileGroup = findViewById(R.id.mobile_combo); mMobileRoaming = findViewById(R.id.mobile_roaming); mMobileSignal = findViewById(R.id.mobile_signal); - mCarrierText = findViewById(R.id.qs_carrier_text); + mCarrierText = findViewById(R.id.shade_carrier_text); mSpacer = findViewById(R.id.spacer); updateResources(); } @@ -158,7 +158,7 @@ public class QSCarrier extends LinearLayout { mCarrierText.setMaxEms( useLargeScreenHeader ? Integer.MAX_VALUE - : getResources().getInteger(R.integer.qs_carrier_max_em) + : getResources().getInteger(R.integer.shade_carrier_max_em) ); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java index a36035b99b4f..68561d1cfd0f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier; +package com.android.systemui.shade.carrier; import android.annotation.StyleRes; import android.content.Context; @@ -27,10 +27,10 @@ import com.android.systemui.FontSizeUtils; import com.android.systemui.R; /** - * Displays Carrier name and network status in QS + * Displays Carrier name and network status in the shade header */ -public class QSCarrierGroup extends LinearLayout { - public QSCarrierGroup(Context context, AttributeSet attrs) { +public class ShadeCarrierGroup extends LinearLayout { + public ShadeCarrierGroup(Context context, AttributeSet attrs) { super(context, attrs); } @@ -38,24 +38,24 @@ public class QSCarrierGroup extends LinearLayout { return findViewById(R.id.no_carrier_text); } - QSCarrier getCarrier1View() { + ShadeCarrier getCarrier1View() { return findViewById(R.id.carrier1); } - QSCarrier getCarrier2View() { + ShadeCarrier getCarrier2View() { return findViewById(R.id.carrier2); } - QSCarrier getCarrier3View() { + ShadeCarrier getCarrier3View() { return findViewById(R.id.carrier3); } View getCarrierDivider1() { - return findViewById(R.id.qs_carrier_divider1); + return findViewById(R.id.shade_carrier_divider1); } View getCarrierDivider2() { - return findViewById(R.id.qs_carrier_divider2); + return findViewById(R.id.shade_carrier_divider2); } public void updateTextAppearance(@StyleRes int resId) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java index 6a8bf759a849..0ebcfa2a1876 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier; +package com.android.systemui.shade.carrier; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; @@ -52,8 +52,8 @@ import java.util.function.Consumer; import javax.inject.Inject; -public class QSCarrierGroupController { - private static final String TAG = "QSCarrierGroup"; +public class ShadeCarrierGroupController { + private static final String TAG = "ShadeCarrierGroup"; /** * Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount} @@ -72,7 +72,7 @@ public class QSCarrierGroupController { private final CellSignalState[] mInfos = new CellSignalState[SIM_SLOTS]; private View[] mCarrierDividers = new View[SIM_SLOTS - 1]; - private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS]; + private ShadeCarrier[] mCarrierGroups = new ShadeCarrier[SIM_SLOTS]; private int[] mLastSignalLevel = new int[SIM_SLOTS]; private String[] mLastSignalLevelDescription = new String[SIM_SLOTS]; private final CarrierConfigTracker mCarrierConfigTracker; @@ -129,7 +129,7 @@ public class QSCarrierGroupController { } } - private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter, + private ShadeCarrierGroupController(ShadeCarrierGroup view, ActivityStarter activityStarter, @Background Handler bgHandler, @Main Looper mainLooper, NetworkController networkController, CarrierTextManager.Builder carrierTextManagerBuilder, Context context, @@ -167,7 +167,7 @@ public class QSCarrierGroupController { for (int i = 0; i < SIM_SLOTS; i++) { mInfos[i] = new CellSignalState( true, - R.drawable.ic_qs_no_calling_sms, + R.drawable.ic_shade_no_calling_sms, context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(), "", false); @@ -257,7 +257,7 @@ public class QSCarrierGroupController { if (singleCarrier) { for (int i = 0; i < SIM_SLOTS; i++) { if (mInfos[i].visible - && mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) { + && mInfos[i].mobileSignalIconId == R.drawable.ic_shade_sim_card) { mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false); } } @@ -322,8 +322,8 @@ public class QSCarrierGroupController { Log.e(TAG, "Carrier information arrays not of same length"); } } else { - // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show - // info.carrierText in a different view. + // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup, + // instead just show info.carrierText in a different view. for (int i = 0; i < SIM_SLOTS; i++) { mInfos[i] = mInfos[i].changeVisibility(false); mCarrierGroups[i].setCarrierText(""); @@ -368,7 +368,7 @@ public class QSCarrierGroupController { } public static class Builder { - private QSCarrierGroup mView; + private ShadeCarrierGroup mView; private final ActivityStarter mActivityStarter; private final Handler mHandler; private final Looper mLooper; @@ -393,13 +393,13 @@ public class QSCarrierGroupController { mSlotIndexResolver = slotIndexResolver; } - public Builder setQSCarrierGroup(QSCarrierGroup view) { + public Builder setShadeCarrierGroup(ShadeCarrierGroup view) { mView = view; return this; } - public QSCarrierGroupController build() { - return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper, + public ShadeCarrierGroupController build() { + return new ShadeCarrierGroupController(mView, mActivityStarter, mHandler, mLooper, mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker, mSlotIndexResolver); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index c84894fc81ee..06f43f1eeaa5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -841,6 +841,19 @@ public final class KeyboardShortcutListSearch { BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet); behavior.setState(BottomSheetBehavior.STATE_EXPANDED); behavior.setSkipCollapsed(true); + behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_DRAGGING) { + behavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + // Do nothing. + } + }); mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true); Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 765c93ed209b..142689e88b51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1127,13 +1127,7 @@ public class KeyguardIndicationController { final boolean faceAuthUnavailable = biometricSourceType == FACE && msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE; - // TODO(b/141025588): refactor to reduce repetition of code/comments - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - if (!mKeyguardUpdateMonitor - .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) + if (isPrimaryAuthRequired() && !faceAuthUnavailable) { return; } @@ -1234,7 +1228,7 @@ public class KeyguardIndicationController { private void onFaceAuthError(int msgId, String errString) { CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage(); mFaceAcquiredMessageDeferral.reset(); - if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) { + if (shouldSuppressFaceError(msgId)) { mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString); return; } @@ -1248,7 +1242,7 @@ public class KeyguardIndicationController { } private void onFingerprintAuthError(int msgId, String errString) { - if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) { + if (shouldSuppressFingerprintError(msgId)) { mKeyguardLogger.logBiometricMessage("suppressingFingerprintError", msgId, errString); @@ -1257,31 +1251,19 @@ public class KeyguardIndicationController { } } - private boolean shouldSuppressFingerprintError(int msgId, - KeyguardUpdateMonitor updateMonitor) { - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) - && !isLockoutError(msgId)) + private boolean shouldSuppressFingerprintError(int msgId) { + return ((isPrimaryAuthRequired() && !isLockoutError(msgId)) || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED); } - private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) { - // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong - // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to - // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the - // check of whether non-strong biometric is allowed - return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) - && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) + private boolean shouldSuppressFaceError(int msgId) { + return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) || msgId == FaceManager.FACE_ERROR_CANCELED || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS); } - @Override public void onTrustChanged(int userId) { if (!isCurrentUser(userId)) return; @@ -1355,6 +1337,16 @@ public class KeyguardIndicationController { } } + private boolean isPrimaryAuthRequired() { + // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong + // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to + // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the + // check of whether non-strong biometric is allowed since strong biometrics can still be + // used. + return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + true /* isStrongBiometric */); + } + protected boolean isPluggedInAndCharging() { return mPowerPluggedIn; } @@ -1431,7 +1423,7 @@ public class KeyguardIndicationController { private boolean canUnlockWithFingerprint() { return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser()); + getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed(); } private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java index cb4ae286d5c3..f7d37e6b1058 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; -import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -35,7 +34,7 @@ import javax.inject.Inject; * Controller class for {@link NotificationShelf}. */ @NotificationRowScope -public class NotificationShelfController { +public class LegacyNotificationShelfControllerImpl implements NotificationShelfController { private final NotificationShelf mView; private final ActivatableNotificationViewController mActivatableNotificationViewController; private final KeyguardBypassController mKeyguardBypassController; @@ -44,7 +43,7 @@ public class NotificationShelfController { private AmbientState mAmbientState; @Inject - public NotificationShelfController( + public LegacyNotificationShelfControllerImpl( NotificationShelf notificationShelf, ActivatableNotificationViewController activatableNotificationViewController, KeyguardBypassController keyguardBypassController, @@ -79,56 +78,42 @@ public class NotificationShelfController { } } + @Override public NotificationShelf getView() { return mView; } + @Override public boolean canModifyColorOfNotifications() { return mAmbientState.isShadeExpanded() && !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled()); } + @Override public NotificationIconContainer getShelfIcons() { return mView.getShelfIcons(); } - public @View.Visibility int getVisibility() { - return mView.getVisibility(); - } - - public void setCollapsedIcons(NotificationIconContainer notificationIcons) { - mView.setCollapsedIcons(notificationIcons); - } - + @Override public void bind(AmbientState ambientState, NotificationStackScrollLayoutController notificationStackScrollLayoutController) { mView.bind(ambientState, notificationStackScrollLayoutController); mAmbientState = ambientState; } - public int getHeight() { - return mView.getHeight(); - } - - public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, - AmbientState ambientState) { - mAmbientState = ambientState; - mView.updateState(algorithmState, ambientState); - } - + @Override public int getIntrinsicHeight() { return mView.getIntrinsicHeight(); } + @Override public void setOnActivatedListener(ActivatableNotificationView.OnActivatedListener listener) { mView.setOnActivatedListener(listener); } + @Override public void setOnClickListener(View.OnClickListener onClickListener) { mView.setOnClickListener(onClickListener); } - public int getNotGoneIndex() { - return mView.getNotGoneIndex(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 4873c9dae89a..e6715a133838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -64,8 +64,7 @@ import java.io.PrintWriter; * A notification shelf view that is placed inside the notification scroller. It manages the * overflow icons that don't fit into the regular list anymore. */ -public class NotificationShelf extends ActivatableNotificationView implements - View.OnLayoutChangeListener, StateListener { +public class NotificationShelf extends ActivatableNotificationView implements StateListener { private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag; private static final String TAG = "NotificationShelf"; @@ -78,7 +77,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll"); private NotificationIconContainer mShelfIcons; - private int[] mTmp = new int[2]; private boolean mHideBackground; private int mStatusBarHeight; private boolean mEnableNotificationClipping; @@ -87,7 +85,6 @@ public class NotificationShelf extends ActivatableNotificationView implements private int mPaddingBetweenElements; private int mNotGoneIndex; private boolean mHasItemsInStableShelf; - private NotificationIconContainer mCollapsedIcons; private int mScrollFastThreshold; private int mStatusBarState; private boolean mInteractive; @@ -868,10 +865,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return mShelfIcons.getIconState(icon); } - private float getFullyClosedTranslation() { - return -(getIntrinsicHeight() - mStatusBarHeight) / 2; - } - @Override public boolean hasNoContentHeight() { return true; @@ -893,7 +886,6 @@ public class NotificationShelf extends ActivatableNotificationView implements @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - updateRelativeOffset(); // we always want to clip to our sides, such that nothing can draw outside of these bounds int height = getResources().getDisplayMetrics().heightPixels; @@ -903,13 +895,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } } - private void updateRelativeOffset() { - if (mCollapsedIcons != null) { - mCollapsedIcons.getLocationOnScreen(mTmp); - } - getLocationOnScreen(mTmp); - } - /** * @return the index of the notification at which the shelf visually resides */ @@ -924,19 +909,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } } - /** - * @return whether the shelf has any icons in it when a potential animation has finished, i.e - * if the current state would be applied right now - */ - public boolean hasItemsInStableShelf() { - return mHasItemsInStableShelf; - } - - public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { - mCollapsedIcons = collapsedIcons; - mCollapsedIcons.addOnLayoutChangeListener(this); - } - @Override public void onStateChanged(int newState) { mStatusBarState = newState; @@ -983,12 +955,6 @@ public class NotificationShelf extends ActivatableNotificationView implements } @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, - int oldTop, int oldRight, int oldBottom) { - updateRelativeOffset(); - } - - @Override public boolean needsClippingToShelf() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt new file mode 100644 index 000000000000..bf3d47c4a9ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar + +import android.view.View +import android.view.View.OnClickListener +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener +import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.NotificationIconContainer + +/** Controller interface for [NotificationShelf]. */ +interface NotificationShelfController { + /** The [NotificationShelf] controlled by this Controller. */ + val view: NotificationShelf + + /** @see ExpandableView.getIntrinsicHeight */ + val intrinsicHeight: Int + + /** Container view for icons displayed in the shelf. */ + val shelfIcons: NotificationIconContainer + + /** Whether or not the shelf can modify the color of notifications in the shade. */ + fun canModifyColorOfNotifications(): Boolean + + /** @see ActivatableNotificationView.setOnActivatedListener */ + fun setOnActivatedListener(listener: OnActivatedListener) + + /** Binds the shelf to the host [NotificationStackScrollLayout], via its Controller. */ + fun bind( + ambientState: AmbientState, + notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + ) + + /** @see View.setOnClickListener */ + fun setOnClickListener(listener: OnClickListener) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 565c0a9426ab..34300c731343 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -38,8 +38,8 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.qs.carrier.QSCarrierGroupController; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.MediaArtworkProcessor; @@ -78,14 +78,14 @@ import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; +import java.util.Optional; +import java.util.concurrent.Executor; + import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; -import java.util.Optional; -import java.util.concurrent.Executor; - /** * This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to * this separate from {@link CentralSurfacesModule} module so that components that wish to build @@ -271,8 +271,8 @@ public interface CentralSurfacesDependenciesModule { /** */ @Binds - QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( - QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl); + ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( + ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl); /** */ 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/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index 15ad312b413e..1631ae28bf5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.icon.ConversationIconManager @@ -40,27 +41,28 @@ import javax.inject.Inject */ @CoordinatorScope class ConversationCoordinator @Inject constructor( - private val peopleNotificationIdentifier: PeopleNotificationIdentifier, - private val conversationIconManager: ConversationIconManager, - @PeopleHeader peopleHeaderController: NodeController + private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val conversationIconManager: ConversationIconManager, + private val highPriorityProvider: HighPriorityProvider, + @PeopleHeader private val peopleHeaderController: NodeController, ) : Coordinator { private val promotedEntriesToSummaryOfSameChannel = - mutableMapOf<NotificationEntry, NotificationEntry>() + mutableMapOf<NotificationEntry, NotificationEntry>() private val onBeforeRenderListListener = OnBeforeRenderListListener { _ -> val unimportantSummaries = promotedEntriesToSummaryOfSameChannel - .mapNotNull { (promoted, summary) -> - val originalGroup = summary.parent - when { - originalGroup == null -> null - originalGroup == promoted.parent -> null - originalGroup.parent == null -> null - originalGroup.summary != summary -> null - originalGroup.children.any { it.channel == summary.channel } -> null - else -> summary.key + .mapNotNull { (promoted, summary) -> + val originalGroup = summary.parent + when { + originalGroup == null -> null + originalGroup == promoted.parent -> null + originalGroup.parent == null -> null + originalGroup.summary != summary -> null + originalGroup.children.any { it.channel == summary.channel } -> null + else -> summary.key + } } - } conversationIconManager.setUnimportantConversations(unimportantSummaries) promotedEntriesToSummaryOfSameChannel.clear() } @@ -78,21 +80,23 @@ class ConversationCoordinator @Inject constructor( } } - val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) { + val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean = - isConversation(entry) + highPriorityProvider.isHighPriorityConversation(entry) - override fun getComparator() = object : NotifComparator("People") { - override fun compare(entry1: ListEntry, entry2: ListEntry): Int { - val type1 = getPeopleType(entry1) - val type2 = getPeopleType(entry2) - return type2.compareTo(type1) - } - } + override fun getComparator(): NotifComparator = notifComparator - override fun getHeaderNodeController() = - // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController - if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null + override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController + } + + val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) { + // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting. + // All remaining conversations must be silent. + override fun isInSection(entry: ListEntry): Boolean = isConversation(entry) + + override fun getComparator(): NotifComparator = notifComparator + + override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController } override fun attach(pipeline: NotifPipeline) { @@ -101,15 +105,27 @@ class ConversationCoordinator @Inject constructor( } private fun isConversation(entry: ListEntry): Boolean = - getPeopleType(entry) != TYPE_NON_PERSON + getPeopleType(entry) != TYPE_NON_PERSON @PeopleNotificationType private fun getPeopleType(entry: ListEntry): Int = - entry.representativeEntry?.let { - peopleNotificationIdentifier.getPeopleNotificationType(it) - } ?: TYPE_NON_PERSON + entry.representativeEntry?.let { + peopleNotificationIdentifier.getPeopleNotificationType(it) + } ?: TYPE_NON_PERSON + + private val notifComparator: NotifComparator = object : NotifComparator("People") { + override fun compare(entry1: ListEntry, entry2: ListEntry): Int { + val type1 = getPeopleType(entry1) + val type2 = getPeopleType(entry2) + return type2.compareTo(type1) + } + } + + // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController + private val conversationHeaderNodeController: NodeController? = + if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null - companion object { + private companion object { private const val TAG = "ConversationCoordinator" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 6bb5b9218ed7..02ce0d46ead8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -21,6 +21,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable import com.android.systemui.statusbar.notification.collection.PipelineDumper import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider import javax.inject.Inject /** @@ -32,6 +33,7 @@ interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope class NotifCoordinatorsImpl @Inject constructor( notifPipelineFlags: NotifPipelineFlags, + sectionStyleProvider: SectionStyleProvider, dataStoreCoordinator: DataStoreCoordinator, hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, @@ -56,7 +58,7 @@ class NotifCoordinatorsImpl @Inject constructor( viewConfigCoordinator: ViewConfigCoordinator, visualStabilityCoordinator: VisualStabilityCoordinator, sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator + dismissibilityCoordinator: DismissibilityCoordinator, ) : NotifCoordinators { private val mCoordinators: MutableList<Coordinator> = ArrayList() @@ -99,13 +101,20 @@ class NotifCoordinatorsImpl @Inject constructor( mCoordinators.add(dismissibilityCoordinator) // Manually add Ordered Sections - // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default - mOrderedSections.add(headsUpCoordinator.sectioner) + mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService - mOrderedSections.add(conversationCoordinator.sectioner) // People + mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting + mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized + + sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner)) + sectionStyleProvider.setSilentSections(listOf( + conversationCoordinator.peopleSilentSectioner, + rankingCoordinator.silentSectioner, + rankingCoordinator.minimizedSectioner, + )) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java index ea5cb308a2d0..1d37dcf13037 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java @@ -27,15 +27,12 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; import com.android.systemui.statusbar.notification.dagger.AlertingHeader; import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import javax.inject.Inject; @@ -52,7 +49,6 @@ public class RankingCoordinator implements Coordinator { public static final boolean SHOW_ALL_SECTIONS = false; private final StatusBarStateController mStatusBarStateController; private final HighPriorityProvider mHighPriorityProvider; - private final SectionStyleProvider mSectionStyleProvider; private final NodeController mSilentNodeController; private final SectionHeaderController mSilentHeaderController; private final NodeController mAlertingHeaderController; @@ -63,13 +59,11 @@ public class RankingCoordinator implements Coordinator { public RankingCoordinator( StatusBarStateController statusBarStateController, HighPriorityProvider highPriorityProvider, - SectionStyleProvider sectionStyleProvider, @AlertingHeader NodeController alertingHeaderController, @SilentHeader SectionHeaderController silentHeaderController, @SilentHeader NodeController silentNodeController) { mStatusBarStateController = statusBarStateController; mHighPriorityProvider = highPriorityProvider; - mSectionStyleProvider = sectionStyleProvider; mAlertingHeaderController = alertingHeaderController; mSilentNodeController = silentNodeController; mSilentHeaderController = silentHeaderController; @@ -78,9 +72,6 @@ public class RankingCoordinator implements Coordinator { @Override public void attach(NotifPipeline pipeline) { mStatusBarStateController.addCallback(mStatusBarStateCallback); - mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner)); - mSectionStyleProvider.setSilentSections( - Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner)); pipeline.addPreGroupFilter(mSuspendedFilter); pipeline.addPreGroupFilter(mDndVisualEffectsFilter); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java index e7ef2ec084b7..731ec80817ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.notification.collection.provider; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -63,7 +66,7 @@ public class HighPriorityProvider { * A GroupEntry is considered high priority if its representativeEntry (summary) or children are * high priority */ - public boolean isHighPriority(ListEntry entry) { + public boolean isHighPriority(@Nullable ListEntry entry) { if (entry == null) { return false; } @@ -78,6 +81,36 @@ public class HighPriorityProvider { || hasHighPriorityChild(entry); } + /** + * @return true if the ListEntry is high priority conversation, else false + */ + public boolean isHighPriorityConversation(@NonNull ListEntry entry) { + final NotificationEntry notifEntry = entry.getRepresentativeEntry(); + if (notifEntry == null) { + return false; + } + + if (!isPeopleNotification(notifEntry)) { + return false; + } + + if (notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT) { + return true; + } + + return isNotificationEntryWithAtLeastOneImportantChild(entry); + } + + private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) { + if (!(entry instanceof GroupEntry)) { + return false; + } + final GroupEntry groupEntry = (GroupEntry) entry; + return groupEntry.getChildren().stream().anyMatch( + childEntry -> + childEntry.getRanking().getImportance() + >= NotificationManager.IMPORTANCE_DEFAULT); + } private boolean hasHighPriorityChild(ListEntry entry) { if (entry instanceof NotificationEntry @@ -93,7 +126,6 @@ public class HighPriorityProvider { } } } - return false; } 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/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt new file mode 100644 index 000000000000..f2216fce6fef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.interruption + +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision + +/** + * Wraps a [NotificationInterruptStateProvider] to convert it to the new + * [VisualInterruptionDecisionProvider] interface. + */ +@SysUISingleton +class NotificationInterruptStateProviderWrapper( + private val wrapped: NotificationInterruptStateProvider +) : VisualInterruptionDecisionProvider { + + @VisibleForTesting + enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision { + SHOULD_INTERRUPT(shouldInterrupt = true), + SHOULD_NOT_INTERRUPT(shouldInterrupt = false); + + companion object { + fun of(booleanDecision: Boolean) = + if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT + } + } + + @VisibleForTesting + class FullScreenIntentDecisionImpl( + val originalEntry: NotificationEntry, + val originalDecision: NotificationInterruptStateProvider.FullScreenIntentDecision + ) : FullScreenIntentDecision { + override val shouldInterrupt = originalDecision.shouldLaunch + override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND + } + + override fun addSuppressor(suppressor: NotificationInterruptSuppressor) { + wrapped.addSuppressor(suppressor) + } + + override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision = + wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) } + + override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision = + wrapped.checkHeadsUp(entry, /* log= */ true).let { DecisionImpl.of(it) } + + override fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry) = + wrapped.getFullScreenIntentDecision(entry).let { FullScreenIntentDecisionImpl(entry, it) } + + override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) { + (decision as FullScreenIntentDecisionImpl).let { + wrapped.logFullScreenIntentDecision(it.originalEntry, it.originalDecision) + } + } + + override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision = + wrapped.shouldBubbleUp(entry).let { DecisionImpl.of(it) } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt new file mode 100644 index 000000000000..c0f4fcda56bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.interruption + +import com.android.systemui.statusbar.notification.collection.NotificationEntry + +/** + * Decides whether a notification should visually interrupt the user in various ways. + * + * These include displaying the notification as heads-up (peeking while the device is awake or + * pulsing while the device is dozing), displaying the notification as a bubble, and launching a + * full-screen intent for the notification. + */ +interface VisualInterruptionDecisionProvider { + /** + * Represents the decision to visually interrupt or not. + * + * Used for heads-up and bubble decisions; subclassed by [FullScreenIntentDecision] for + * full-screen intent decisions. + * + * @property[shouldInterrupt] whether a visual interruption should be triggered + */ + interface Decision { + val shouldInterrupt: Boolean + } + + /** + * Represents the decision to launch a full-screen intent for a notification or not. + * + * @property[wouldInterruptWithoutDnd] whether a full-screen intent should not be launched only + * because Do Not Disturb has suppressed it + */ + interface FullScreenIntentDecision : Decision { + val wouldInterruptWithoutDnd: Boolean + } + + /** + * Adds a [component][suppressor] that can suppress visual interruptions. + * + * This class may call suppressors in any order. + * + * @param[suppressor] the suppressor to add + */ + fun addSuppressor(suppressor: NotificationInterruptSuppressor) + + /** + * Decides whether a [notification][entry] should display as heads-up or not, but does not log + * that decision. + * + * @param[entry] the notification that this decision is about + * @return the decision to display that notification as heads-up or not + */ + fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision + + /** + * Decides whether a [notification][entry] should display as heads-up or not, and logs that + * decision. + * + * If the device is awake, the decision will consider whether the notification should "peek" + * (slide in from the top of the screen over the current activity). + * + * If the device is dozing, the decision will consider whether the notification should "pulse" + * (wake the screen up and display the ambient view of the notification). + * + * @see[makeUnloggedHeadsUpDecision] + * + * @param[entry] the notification that this decision is about + * @return the decision to display that notification as heads-up or not + */ + fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision + + /** + * Decides whether a [notification][entry] should launch a full-screen intent or not, but does + * not log that decision. + * + * The returned decision can be logged by passing it to [logFullScreenIntentDecision]. + * + * @see[makeAndLogHeadsUpDecision] + * + * @param[entry] the notification that this decision is about + * @return the decision to launch a full-screen intent for that notification or not + */ + fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision + + /** + * Logs a previous [decision] to launch a full-screen intent or not. + * + * @param[decision] the decision to log + */ + fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) + + /** + * Decides whether a [notification][entry] should display as a bubble or not. + * + * @param[entry] the notification that this decision is about + * @return the decision to display that notification as a bubble or not + */ + fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java index af8d6ec727d1..98cd84dde199 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.row.dagger; +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import dagger.Binds; @@ -46,7 +46,8 @@ public interface NotificationShelfComponent { * Creates a NotificationShelfController. */ @NotificationRowScope - NotificationShelfController getNotificationShelfController(); + LegacyNotificationShelfControllerImpl getNotificationShelfController(); + /** * Dagger Module that extracts interesting properties from a NotificationShelf. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt new file mode 100644 index 000000000000..a2351578ec98 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt @@ -0,0 +1,152 @@ +/* + * 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.notification.shelf.view + +import android.view.View +import android.view.View.OnAttachStateChangeListener +import android.view.accessibility.AccessibilityManager +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.NotificationShelfController +import com.android.systemui.statusbar.SysuiStatusBarStateController +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView +import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController +import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController +import com.android.systemui.statusbar.notification.row.ExpandableViewController +import com.android.systemui.statusbar.notification.stack.AmbientState +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.phone.NotificationIconContainer +import com.android.systemui.statusbar.phone.NotificationTapHelper +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import dagger.Binds +import dagger.Module +import javax.inject.Inject + +/** Binds a [NotificationShelf] to its backend. */ +interface NotificationShelfViewBinder { + fun bind(shelf: NotificationShelf) +} + +/** + * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper + * around a [NotificationShelfViewBinder], so that external code can continue to depend on the + * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is + * removed, this class can go away and the ViewBinder can be used directly. + */ +@CentralSurfacesScope +class NotificationShelfViewBinderWrapperControllerImpl +@Inject +constructor( + private val shelf: NotificationShelf, + private val viewBinder: NotificationShelfViewBinder, + private val keyguardBypassController: KeyguardBypassController, + featureFlags: FeatureFlags, + private val notifTapHelperFactory: NotificationTapHelper.Factory, + private val a11yManager: AccessibilityManager, + private val falsingManager: FalsingManager, + private val falsingCollector: FalsingCollector, + private val statusBarStateController: SysuiStatusBarStateController, +) : NotificationShelfController { + + private var ambientState: AmbientState? = null + + override val view: NotificationShelf + get() = shelf + + init { + shelf.apply { + useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)) + setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)) + } + } + + fun init() { + viewBinder.bind(shelf) + + ActivatableNotificationViewController( + shelf, + notifTapHelperFactory, + ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)), + a11yManager, + falsingManager, + falsingCollector, + ) + .init() + shelf.setController(this) + val onAttachStateListener = + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + statusBarStateController.addCallback( + shelf, + SysuiStatusBarStateController.RANK_SHELF, + ) + } + + override fun onViewDetachedFromWindow(v: View) { + statusBarStateController.removeCallback(shelf) + } + } + shelf.addOnAttachStateChangeListener(onAttachStateListener) + if (shelf.isAttachedToWindow) { + onAttachStateListener.onViewAttachedToWindow(shelf) + } + } + + override val intrinsicHeight: Int + get() = shelf.intrinsicHeight + + override val shelfIcons: NotificationIconContainer + get() = shelf.shelfIcons + + override fun canModifyColorOfNotifications(): Boolean { + return (ambientState?.isShadeExpanded == true && + !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled)) + } + + override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) { + shelf.setOnActivatedListener(listener) + } + + override fun bind( + ambientState: AmbientState, + notificationStackScrollLayoutController: NotificationStackScrollLayoutController + ) { + shelf.bind(ambientState, notificationStackScrollLayoutController) + this.ambientState = ambientState + } + + override fun setOnClickListener(listener: View.OnClickListener) { + shelf.setOnClickListener(listener) + } +} + +@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule + +@Module +private interface PrivateShelfViewBinderModule { + @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder +} + +@CentralSurfacesScope +private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder { + override fun bind(shelf: NotificationShelf) {} +} 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 0c8e9e56b04a..7596ce08a53c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -192,6 +192,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shared.recents.utilities.Utilities; +import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CircleReveal; @@ -505,6 +506,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { /** Controller for the Shade. */ @VisibleForTesting NotificationPanelViewController mNotificationPanelViewController; + private final ShadeLogger mShadeLogger; // settings private QSPanelController mQSPanelController; @@ -738,6 +740,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { KeyguardViewMediator keyguardViewMediator, DisplayMetrics displayMetrics, MetricsLogger metricsLogger, + ShadeLogger shadeLogger, @UiBackground Executor uiBgExecutor, NotificationMediaManager notificationMediaManager, NotificationLockscreenUserManager lockScreenUserManager, @@ -830,6 +833,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mKeyguardViewMediator = keyguardViewMediator; mDisplayMetrics = displayMetrics; mMetricsLogger = metricsLogger; + mShadeLogger = shadeLogger; mUiBgExecutor = uiBgExecutor; mMediaManager = notificationMediaManager; mLockscreenUserManager = lockScreenUserManager; @@ -3672,6 +3676,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing()) || goingToSleepWithoutAnimation || mDeviceProvisionedController.isFrpActive(); + mShadeLogger.logUpdateNotificationPanelTouchState(disabled, isGoingToSleep(), + !mDozeParameters.shouldControlScreenOff(), !mDeviceInteractive, + !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive()); + mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled); mNotificationIconAreaController.setAnimationsEnabled(!disabled); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt index e4227dce94e7..d433814d7ce4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt @@ -29,6 +29,7 @@ import com.android.systemui.R import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.VibratorHelper @@ -57,7 +58,7 @@ constructor( } private var ambientIndicationArea: View? = null - private lateinit var binding: KeyguardBottomAreaViewBinder.Binding + private var binding: KeyguardBottomAreaViewBinder.Binding? = null private var lockIconViewController: LockIconViewController? = null /** Initializes the view. */ @@ -67,13 +68,16 @@ constructor( lockIconViewController: LockIconViewController? = null, messageDisplayer: MessageDisplayer? = null, vibratorHelper: VibratorHelper? = null, + activityStarter: ActivityStarter? = null, ) { + binding?.destroy() binding = bind( this, viewModel, falsingManager, vibratorHelper, + activityStarter, ) { messageDisplayer?.display(it) } @@ -114,12 +118,12 @@ constructor( override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - binding.onConfigurationChanged() + binding?.onConfigurationChanged() } /** Returns a list of animators to use to animate the indication areas. */ val indicationAreaAnimators: List<ViewPropertyAnimator> - get() = binding.getIndicationAreaAnimators() + get() = checkNotNull(binding).getIndicationAreaAnimators() override fun hasOverlappingRendering(): Boolean { return false @@ -139,7 +143,7 @@ constructor( super.onLayout(changed, left, top, right, bottom) findViewById<View>(R.id.ambient_indication_container)?.let { val (ambientLeft, ambientTop) = it.locationOnScreen - if (binding.shouldConstrainToTopOfLockIcon()) { + if (binding?.shouldConstrainToTopOfLockIcon() == true) { // make top of ambient indication view the bottom of the lock icon it.layout( ambientLeft, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java deleted file mode 100644 index 076e5f1c1ce7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 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.phone; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.WindowInsets; -import android.widget.FrameLayout; - -/** - * A view group which contains the preview of phone/camera and draws a black bar at the bottom as - * the fake navigation bar. - */ -public class KeyguardPreviewContainer extends FrameLayout { - - private Drawable mBlackBarDrawable = new Drawable() { - @Override - public void draw(Canvas canvas) { - canvas.save(); - canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight()); - canvas.drawColor(Color.BLACK); - canvas.restore(); - } - - @Override - public void setAlpha(int alpha) { - // noop - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - // noop - } - - @Override - public int getOpacity() { - return android.graphics.PixelFormat.OPAQUE; - } - }; - - public KeyguardPreviewContainer(Context context, AttributeSet attrs) { - super(context, attrs); - setBackground(mBlackBarDrawable); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - setPadding(0, 0, 0, insets.getStableInsetBottom()); - return super.onApplyWindowInsets(insets); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index eb19c0d2ad71..057fa42bd347 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -193,7 +193,6 @@ public class NotificationIconAreaController implements public void setupShelf(NotificationShelfController notificationShelfController) { mShelfIcons = notificationShelfController.getShelfIcons(); - notificationShelfController.setCollapsedIcons(mNotificationIcons); } public void onDensityOrFontScaleChanged(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt index b3031515ae9d..c8174669cc65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt @@ -85,17 +85,16 @@ class ScreenOffAnimationController @Inject constructor( /** * Called when keyguard is about to be displayed and allows to perform custom animation - * - * @return A handle that can be used for cancelling the animation, if necessary */ - fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? { - animations.forEach { + fun animateInKeyguard(keyguardView: View, after: Runnable) = + animations.firstOrNull { if (it.shouldAnimateInKeyguard()) { - return@animateInKeyguard it.animateInKeyguard(keyguardView, after) + it.animateInKeyguard(keyguardView, after) + true + } else { + false } } - return null - } /** * If returns true it will disable propagating touches to apps and keyguard @@ -212,10 +211,7 @@ interface ScreenOffAnimation { fun onAlwaysOnChanged(alwaysOn: Boolean) {} fun shouldAnimateInKeyguard(): Boolean = false - fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? { - after.run() - return null - } + fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run() fun shouldDelayKeyguardShow(): Boolean = false fun isKeyguardShowDelayed(): Boolean = false @@ -228,7 +224,3 @@ interface ScreenOffAnimation { fun shouldAnimateDozingChange(): Boolean = true fun shouldAnimateClockChange(): Boolean = true } - -interface AnimatorHandle { - fun cancel() -}
\ No newline at end of file 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/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index de7bf3c021dd..d731f8886536 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -230,7 +230,7 @@ public class StatusBarSignalPolicy implements SignalCallback, if (state == null) { return; } - if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) { + if (statusIcon.icon == R.drawable.ic_shade_no_calling_sms) { state.isNoCalling = statusIcon.visible; state.noCallingDescription = statusIcon.contentDescription; } else { @@ -422,7 +422,7 @@ public class StatusBarSignalPolicy implements SignalCallback, private CallIndicatorIconState(int subId) { this.subId = subId; - this.noCallingResId = R.drawable.ic_qs_no_calling_sms; + this.noCallingResId = R.drawable.ic_shade_no_calling_sms; this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java index 50cce45cd87a..6dc8065b2822 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java @@ -16,6 +16,12 @@ package com.android.systemui.statusbar.phone; public interface StatusBarWindowCallback { - void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing, - boolean isDozing, boolean panelExpanded, boolean isDreaming); + /** + * Invoked when the internal state of NotificationShadeWindowControllerImpl changes. + * Some of the flags passed as argument to the callback might have changed, but this is not + * guaranteed. + */ + void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, + boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing, + boolean panelExpanded, boolean isDreaming); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index deb041454da4..118bfc55dd4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -160,7 +160,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( * Animates in the provided keyguard view, ending in the same position that it will be in on * AOD. */ - override fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle { + override fun animateInKeyguard(keyguardView: View, after: Runnable) { shouldAnimateInKeyguard = false keyguardView.alpha = 0f keyguardView.visibility = View.VISIBLE @@ -175,36 +175,11 @@ class UnlockedScreenOffAnimationController @Inject constructor( // We animate the Y properly separately using the PropertyAnimator, as the panel // view also needs to update the end position. PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y) + PropertyAnimator.setProperty<View>(keyguardView, AnimatableProperty.Y, currentY, + AnimationProperties().setDuration(duration.toLong()), + true /* animate */) - // Start the animation on the next frame using Choreographer APIs. animateInKeyguard() is - // called while the system is busy processing lots of requests, so delaying the animation a - // frame will mitigate jank. In the event the animation is cancelled before the next frame - // is called, this callback will be removed - val keyguardAnimator = keyguardView.animate() - val nextFrameCallback = TraceUtils.namedRunnable("startAnimateInKeyguard") { - PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY, - AnimationProperties().setDuration(duration.toLong()), - true /* animate */) - keyguardAnimator.start() - } - DejankUtils.postAfterTraversal(nextFrameCallback) - val animatorHandle = object : AnimatorHandle { - private var hasCancelled = false - override fun cancel() { - if (!hasCancelled) { - DejankUtils.removeCallbacks(nextFrameCallback) - // If we're cancelled, reset state flags/listeners. The end action above - // will not be called, which is what we want since that will finish the - // screen off animation and show the lockscreen, which we don't want if we - // were cancelled. - aodUiAnimationPlaying = false - decidedToAnimateGoingToSleep = null - keyguardView.animate().setListener(null) - hasCancelled = true - } - } - } - keyguardAnimator + keyguardView.animate() .setDuration(duration.toLong()) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) @@ -230,7 +205,14 @@ class UnlockedScreenOffAnimationController @Inject constructor( } .setListener(object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator?) { - animatorHandle.cancel() + // If we're cancelled, reset state flags/listeners. The end action above + // will not be called, which is what we want since that will finish the + // screen off animation and show the lockscreen, which we don't want if we + // were cancelled. + aodUiAnimationPlaying = false + decidedToAnimateGoingToSleep = null + keyguardView.animate().setListener(null) + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) } @@ -240,7 +222,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( CUJ_SCREEN_OFF_SHOW_AOD) } }) - return animatorHandle + .start() } override fun onStartedWakingUp() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 0929233feb88..cc2a0ba6f798 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -33,6 +33,7 @@ import com.android.systemui.biometrics.AuthRippleView; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.privacy.OngoingPrivacyChip; import com.android.systemui.settings.UserTracker; @@ -44,12 +45,15 @@ import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationsQuickSettingsContainer; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; +import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule; +import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; @@ -76,13 +80,15 @@ import com.android.systemui.util.settings.SecureSettings; import java.util.concurrent.Executor; import javax.inject.Named; +import javax.inject.Provider; import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoSet; -@Module(subcomponents = StatusBarFragmentComponent.class) +@Module(subcomponents = StatusBarFragmentComponent.class, + includes = { NotificationShelfViewBinderModule.class }) public abstract class StatusBarViewModule { public static final String SHADE_HEADER = "large_screen_shade_header"; @@ -130,16 +136,24 @@ public abstract class StatusBarViewModule { @Provides @CentralSurfacesComponent.CentralSurfacesScope public static NotificationShelfController providesStatusBarWindowView( + FeatureFlags featureFlags, + Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl, NotificationShelfComponent.Builder notificationShelfComponentBuilder, NotificationShelf notificationShelf) { - NotificationShelfComponent component = notificationShelfComponentBuilder - .notificationShelf(notificationShelf) - .build(); - NotificationShelfController notificationShelfController = - component.getNotificationShelfController(); - notificationShelfController.init(); - - return notificationShelfController; + if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get(); + impl.init(); + return impl; + } else { + NotificationShelfComponent component = notificationShelfComponentBuilder + .notificationShelf(notificationShelf) + .build(); + LegacyNotificationShelfControllerImpl notificationShelfController = + component.getNotificationShelfController(); + notificationShelfController.init(); + + return notificationShelfController; + } } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index fa712872eb13..ea77163f0556 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -60,6 +60,13 @@ interface MobileConnectionsRepository { */ val mobileIsDefault: StateFlow<Boolean> + /** + * True if the device currently has a carrier merged connection. + * + * See [CarrierMergedConnectionRepository] for more info. + */ + val hasCarrierMergedConnection: Flow<Boolean> + /** True if the default network connection is validated and false otherwise. */ val defaultConnectionIsValidated: StateFlow<Boolean> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index 44b5b3fa2591..eb20bba0d21f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -159,6 +159,15 @@ constructor( .flatMapLatest { it.mobileIsDefault } .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value) + override val hasCarrierMergedConnection: StateFlow<Boolean> = + activeRepo + .flatMapLatest { it.hasCarrierMergedConnection } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + realRepository.hasCarrierMergedConnection.value, + ) + override val defaultConnectionIsValidated: StateFlow<Boolean> = activeRepo .flatMapLatest { it.defaultConnectionIsValidated } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 737bc6826d08..0e4ceebcc854 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -160,6 +160,9 @@ constructor( override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true) // TODO(b/261029387): not yet supported + override val hasCarrierMergedConnection = MutableStateFlow(false) + + // TODO(b/261029387): not yet supported override val defaultConnectionIsValidated: StateFlow<Boolean> = MutableStateFlow(true) override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 45d50c103909..0e9b6c56437e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -257,18 +258,32 @@ constructor( override val mobileIsDefault: StateFlow<Boolean> = connectivityRepository.defaultConnections - // Because carrier merged networks are displayed as mobile networks, they're - // part of the `isDefault` calculation. See b/272586234. - .map { it.mobile.isDefault || it.carrierMerged.isDefault } + .map { it.mobile.isDefault } .distinctUntilChanged() .logDiffsForTable( tableLogger, - columnPrefix = "", + columnPrefix = LOGGING_PREFIX, columnName = "mobileIsDefault", initialValue = false, ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val hasCarrierMergedConnection: StateFlow<Boolean> = + combine( + connectivityRepository.defaultConnections, + carrierMergedSubId, + ) { defaultConnections, carrierMergedSubId -> + defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null + } + .distinctUntilChanged() + .logDiffsForTable( + tableLogger, + columnPrefix = LOGGING_PREFIX, + columnName = "hasCarrierMergedConnection", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val defaultConnectionIsValidated: StateFlow<Boolean> = connectivityRepository.defaultConnections .map { it.isValidated } 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 1e3122b1a515..eec91a0bca82 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 @@ -113,7 +113,22 @@ constructor( private val context: Context, ) : MobileIconsInteractor { - override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault + override val mobileIsDefault = + combine( + mobileConnectionsRepo.mobileIsDefault, + mobileConnectionsRepo.hasCarrierMergedConnection, + ) { mobileIsDefault, hasCarrierMergedConnection -> + // Because carrier merged networks are displayed as mobile networks, they're part of + // the `isDefault` calculation. See b/272586234. + mobileIsDefault || hasCarrierMergedConnection + } + .logDiffsForTable( + tableLogger, + LOGGING_PREFIX, + columnName = "mobileIsDefault", + initialValue = false, + ) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = mobileConnectionsRepo.activeMobileDataRepository 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 dce7bf21fadd..bfd133e6830c 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 @@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** Common interface for all of the location-based mobile icon view models. */ @@ -80,7 +79,12 @@ constructor( ) : MobileIconViewModelCommon { /** Whether or not to show the error state of [SignalDrawable] */ private val showExclamationMark: Flow<Boolean> = - iconInteractor.isDefaultDataEnabled.mapLatest { !it } + combine( + iconInteractor.isDefaultDataEnabled, + iconInteractor.isDefaultConnectionFailed, + ) { isDefaultDataEnabled, isDefaultConnectionFailed -> + !isDefaultDataEnabled || isDefaultConnectionFailed + } override val isVisible: StateFlow<Boolean> = if (!constants.hasDataCapabilities) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt index 6479f3d9f8a6..731f1e028470 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt @@ -44,11 +44,11 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnecti import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.tuner.TunerService import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -68,12 +68,12 @@ interface ConnectivityRepository { val defaultConnections: StateFlow<DefaultConnectionModel> } -@OptIn(ExperimentalCoroutinesApi::class) +@SuppressLint("MissingPermission") @SysUISingleton class ConnectivityRepositoryImpl @Inject constructor( - connectivityManager: ConnectivityManager, + private val connectivityManager: ConnectivityManager, private val connectivitySlots: ConnectivitySlots, context: Context, dumpManager: DumpManager, @@ -144,15 +144,14 @@ constructor( ) { logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities) + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + val isWifiDefault = - networkCapabilities.hasTransport(TRANSPORT_WIFI) || - networkCapabilities.getMainOrUnderlyingWifiInfo() != null + networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR) - val isCarrierMergedDefault = - networkCapabilities - .getMainOrUnderlyingWifiInfo() - ?.isCarrierMerged == true + val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET) @@ -209,7 +208,32 @@ constructor( * always use [WifiInfo] if it's available, so we need to check the underlying transport * info. */ - fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? { + fun NetworkCapabilities.getMainOrUnderlyingWifiInfo( + connectivityManager: ConnectivityManager, + ): WifiInfo? { + val mainWifiInfo = this.getMainWifiInfo() + if (mainWifiInfo != null) { + return mainWifiInfo + } + // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI, + // so skip the underlying network check if it's not CELLULAR. + if (!this.hasTransport(TRANSPORT_CELLULAR)) { + return mainWifiInfo + } + + // Some connections, like VPN connections, may have underlying networks that are + // eventually traced to a wifi or carrier merged connection. So, check those underlying + // networks for possible wifi information as well. See b/225902574. + return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork -> + connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo() + } + } + + /** + * Checks the network capabilities for wifi info, but does *not* check the underlying + * networks. See [getMainOrUnderlyingWifiInfo]. + */ + private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? { // Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for // virtual networks like VCN. val canHaveWifiInfo = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index 08c14e743bb6..f800cf496ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -33,6 +33,15 @@ interface WifiRepository { /** Observable for the current wifi network activity. */ val wifiActivity: StateFlow<DataActivityModel> + + /** + * Returns true if the device is currently connected to a wifi network with a valid SSID and + * false otherwise. + */ + fun isWifiConnectedWithValidSsid(): Boolean { + val currentNetwork = wifiNetwork.value + return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid() + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index f80aa688268f..b37c44a2f8cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -138,7 +138,8 @@ constructor( wifiNetworkChangeEvents.tryEmit(Unit) - val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo() + val wifiInfo = + networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) if (wifiInfo?.isPrimary == true) { val wifiNetworkModel = createWifiNetworkModel( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt index 96ab074c6e56..1a41abf031bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.pipeline.wifi.domain.interactor -import android.net.wifi.WifiManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel @@ -76,7 +75,7 @@ constructor( when { info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint -> info.passpointProviderFriendlyName - info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid + info.hasValidSsid() -> info.ssid else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 0923d7848d8c..4b33c88cea30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model +import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable @@ -223,6 +224,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } + /** Returns true if this network has a valid SSID and false otherwise. */ + fun hasValidSsid(): Boolean { + return ssid != null && ssid != UNKNOWN_SSID + } + override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal !is Active) { logFull(row) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 654ba04eba7a..1e63b2a4b3e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -21,6 +21,8 @@ import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; import static android.os.BatteryManager.EXTRA_HEALTH; import static android.os.BatteryManager.EXTRA_PRESENT; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; + import android.annotation.WorkerThread; import android.content.BroadcastReceiver; import android.content.Context; @@ -169,7 +171,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void setPowerSaveMode(boolean powerSave, View view) { if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view)); - BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true); + BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true, + SAVER_ENABLED_QS); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index f1269f2b012a..673819b20e4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -38,14 +38,14 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Objects; import javax.inject.Inject; -import dagger.Lazy; - /** */ @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 08f7eaee3011..a4b093d1832a 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -241,8 +241,8 @@ public class BubblesManager { // Store callback in a field so it won't get GC'd mStatusBarWindowCallback = - (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded, - isDreaming) -> + (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing, + panelExpanded, isDreaming) -> mBubbles.onNotificationPanelExpandedChanged(panelExpanded); notificationShadeWindowController.registerCallback(mStatusBarWindowCallback); diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java deleted file mode 100644 index e93e86291535..000000000000 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.core.animation; - -import android.os.Looper; -import android.os.SystemClock; -import android.util.AndroidRuntimeException; - -import androidx.annotation.NonNull; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.util.ArrayList; -import java.util.List; - -/** - * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to - * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static - * list of callbacks. - * - * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code. - */ -public final class AnimatorTestRule2 implements TestRule { - - class TestAnimationHandler extends AnimationHandler { - TestAnimationHandler() { - super(new TestProvider()); - } - - List<AnimationFrameCallback> animationCallbacks = new ArrayList<>(); - - @Override - void addAnimationFrameCallback(AnimationFrameCallback callback) { - animationCallbacks.add(callback); - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void removeCallback(AnimationFrameCallback callback) { - int id = animationCallbacks.indexOf(callback); - if (id >= 0) { - animationCallbacks.set(id, null); - } - } - - void onAnimationFrame(long frameTime) { - for (int i = 0; i < animationCallbacks.size(); i++) { - final AnimationFrameCallback callback = animationCallbacks.get(i); - if (callback == null) { - continue; - } - callback.doAnimationFrame(frameTime); - } - } - - @Override - void autoCancelBasedOn(ObjectAnimator objectAnimator) { - for (int i = animationCallbacks.size() - 1; i >= 0; i--) { - AnimationFrameCallback cb = animationCallbacks.get(i); - if (cb == null) { - continue; - } - if (objectAnimator.shouldAutoCancel(cb)) { - ((Animator) animationCallbacks.get(i)).cancel(); - } - } - } - } - - final TestAnimationHandler mTestHandler; - final long mStartTime; - private long mTotalTimeDelta = 0; - private final Object mLock = new Object(); - - public AnimatorTestRule2() { - mStartTime = SystemClock.uptimeMillis(); - mTestHandler = new TestAnimationHandler(); - } - - @NonNull - @Override - public Statement apply(@NonNull final Statement base, @NonNull Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - AnimationHandler.setTestHandler(mTestHandler); - try { - base.evaluate(); - } finally { - AnimationHandler.setTestHandler(null); - } - } - }; - } - - /** - * Advances the animation clock by the given amount of delta in milliseconds. This call will - * produce an animation frame to all the ongoing animations. This method needs to be - * called on the same thread as {@link Animator#start()}. - * - * @param timeDelta the amount of milliseconds to advance - */ - public void advanceTimeBy(long timeDelta) { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - // Advance time & pulse a frame - mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta; - } - // produce a frame - mTestHandler.onAnimationFrame(getCurrentTime()); - } - - - /** - * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a - * different time than the time tracked by {@link SystemClock} This method needs to be called on - * the same thread as {@link Animator#start()}. - */ - public long getCurrentTime() { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - return mStartTime + mTotalTimeDelta; - } - } - - - private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider { - TestProvider() { - } - - @Override - public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) { - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void postFrameCallback() { - } - - @Override - public void setFrameDelay(long delay) { - } - - @Override - public long getFrameDelay() { - return 0; - } - } -} - diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt index bddd60b5970a..e7738aff6278 100644 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt @@ -30,7 +30,7 @@ import org.junit.runner.RunWith @RunWithLooper(setAsMainLooper = true) class AnimatorTestRuleTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Test fun testA() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index ecf7e0d46373..5557efa97e3e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -38,8 +38,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.pm.PackageManager; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; import android.provider.Settings; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; @@ -52,6 +50,8 @@ import android.text.TextUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; +import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository; +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -88,8 +88,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "", DATA_ROAMING_ENABLE, null, null, null, null, false, null, ""); - @Mock - private WifiManager mWifiManager; + private FakeWifiRepository mWifiRepository = new FakeWifiRepository(); @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock @@ -121,7 +120,6 @@ public class CarrierTextManagerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(WifiManager.class, mWifiManager); mContext.addMockSystemService(PackageManager.class, mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true); mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); @@ -144,7 +142,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { when(mTelephonyManager.getActiveModemCount()).thenReturn(3); mCarrierTextManager = new CarrierTextManager.Builder( - mContext, mContext.getResources(), mWifiManager, + mContext, mContext.getResources(), mWifiRepository, mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor) .setShowAirplaneMode(true) @@ -364,7 +362,11 @@ public class CarrierTextManagerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn( TelephonyManager.SIM_STATE_READY); when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list); - mockWifi(); + + assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); + mWifiRepository.setWifiNetwork( + new WifiNetworkModel.Active(0, false, 0, "", false, false, null)); + assertTrue(mWifiRepository.isWifiConnectedWithValidSsid()); mKeyguardUpdateMonitor.mServiceStates = new HashMap<>(); ServiceState ss = mock(ServiceState.class); @@ -385,13 +387,6 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText); } - private void mockWifi() { - when(mWifiManager.isWifiEnabled()).thenReturn(true); - WifiInfo wifiInfo = mock(WifiInfo.class); - when(wifiInfo.getBSSID()).thenReturn(""); - when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo); - } - @Test public void testCreateInfo_noSubscriptions() { reset(mCarrierTextCallback); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index 48f7d924667e..f1ee1080f6c0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,17 +16,15 @@ package com.android.keyguard; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ClockController; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -45,26 +43,21 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class KeyguardStatusViewControllerTest extends SysuiTestCase { - @Mock - private KeyguardStatusView mKeyguardStatusView; - @Mock - private KeyguardSliceViewController mKeyguardSliceViewController; - @Mock - private KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - ConfigurationController mConfigurationController; - @Mock - DozeParameters mDozeParameters; - @Mock - ScreenOffAnimationController mScreenOffAnimationController; + @Mock private KeyguardStatusView mKeyguardStatusView; + @Mock private KeyguardSliceViewController mKeyguardSliceViewController; + @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private ConfigurationController mConfigurationController; + @Mock private DozeParameters mDozeParameters; + @Mock private ScreenOffAnimationController mScreenOffAnimationController; + @Mock private KeyguardLogger mKeyguardLogger; + @Mock private KeyguardStatusViewController mControllerMock; + @Mock private FeatureFlags mFeatureFlags; + @Mock private InteractionJankMonitor mInteractionJankMonitor; + @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor; - @Mock - KeyguardLogger mKeyguardLogger; private KeyguardStatusViewController mController; @@ -81,7 +74,9 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mConfigurationController, mDozeParameters, mScreenOffAnimationController, - mKeyguardLogger); + mKeyguardLogger, + mFeatureFlags, + mInteractionJankMonitor); } @Test @@ -116,12 +111,4 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onLocaleListChanged(); verify(mKeyguardClockSwitchController).onLocaleListChanged(); } - - @Test - public void getClock_forwardsToClockSwitch() { - ClockController mockClock = mock(ClockController.class); - when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); - - assertEquals(mockClock, mController.getClockController()); - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 08813a7fb48a..3eb9590c0b95 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -20,9 +20,12 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; +import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN; import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP; import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -41,6 +44,8 @@ import static com.android.systemui.statusbar.policy.DevicePostureController.DEVI import static com.google.common.truth.Truth.assertThat; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -79,7 +84,6 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; -import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceAuthenticateOptions; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; @@ -283,33 +287,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Before public void setup() throws RemoteException { MockitoAnnotations.initMocks(this); - - mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false)); - when(mFaceManager.isHardwareDetected()).thenReturn(true); - when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); - when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId); - mFingerprintSensorProperties = List.of( - new FingerprintSensorPropertiesInternal(1 /* sensorId */, - FingerprintSensorProperties.STRENGTH_STRONG, - 1 /* maxEnrollmentsPerUser */, - List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, - "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)), - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - false /* resetLockoutRequiresHAT */)); - when(mFingerprintManager.isHardwareDetected()).thenReturn(true); - when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); - when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn( - mFingerprintSensorProperties); when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true); when(mUserManager.isPrimaryUser()).thenReturn(true); when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class)); when(mStrongAuthTracker - .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */)) + .isUnlockingWithBiometricAllowed(anyBoolean() /* isClass3Biometric */)) .thenReturn(true); when(mTelephonyManager.getServiceStateForSubscriber(anyInt())) .thenReturn(new ServiceState()); @@ -346,20 +330,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyInt()); mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); - - ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor = - ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class); - verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture()); - mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue(); - mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); - - ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor = - ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); - verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( - fingerprintCaptor.capture()); - mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue(); - mFingerprintAuthenticatorsRegisteredCallback - .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); + captureAuthenticatorsRegisteredCallbacks(); + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); verify(mBiometricManager) .registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture()); @@ -381,8 +354,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true); } + private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException { + ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor = + ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class); + verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture()); + mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue(); + mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); + + ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor = + ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class); + verify(mFingerprintManager).addAuthenticatorsRegisteredCallback( + fingerprintCaptor.capture()); + mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue(); + mFingerprintAuthenticatorsRegisteredCallback + .onAllAuthenticatorsRegistered(mFingerprintSensorProperties); + } + + private void setupFaceAuth(boolean isClass3) throws RemoteException { + when(mFaceManager.isHardwareDetected()).thenReturn(true); + when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true); + mFaceSensorProperties = + List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3)); + when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties); + mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); + assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3()); + } + + private void setupFingerprintAuth(boolean isClass3) throws RemoteException { + when(mFingerprintManager.isHardwareDetected()).thenReturn(true); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true); + mFingerprintSensorProperties = List.of( + createFingerprintSensorPropertiesInternal(TYPE_UDFPS_OPTICAL, isClass3)); + when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn( + mFingerprintSensorProperties); + mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered( + mFingerprintSensorProperties); + assertEquals(isClass3, mKeyguardUpdateMonitor.isFingerprintClass3()); + } + + private FingerprintSensorPropertiesInternal createFingerprintSensorPropertiesInternal( + @FingerprintSensorProperties.SensorType int sensorType, + boolean isClass3) { + final List<ComponentInfoInternal> componentInfo = + List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */, + "vendor/model/revision" /* hardwareVersion */, + "1.01" /* firmwareVersion */, + "00000001" /* serialNumber */, "" /* softwareVersion */)); + return new FingerprintSensorPropertiesInternal( + FINGERPRINT_SENSOR_ID, + isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE, + 1 /* maxEnrollmentsPerUser */, + componentInfo, + sensorType, + true /* resetLockoutRequiresHardwareAuthToken */); + } + @NonNull - private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) { + private FaceSensorPropertiesInternal createFaceSensorProperties( + boolean supportsFaceDetection, boolean isClass3) { final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, @@ -391,10 +420,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, "vendor/version/revision" /* softwareVersion */)); - return new FaceSensorPropertiesInternal( - 0 /* id */, - FaceSensorProperties.STRENGTH_STRONG, + FACE_SENSOR_ID /* id */, + isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE, 1 /* maxTemplatesAllowed */, componentInfo, FaceSensorProperties.TYPE_UNKNOWN, @@ -686,7 +714,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() { // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); // THEN unlocking with face and fp is allowed Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -706,12 +734,31 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test - public void testUnlockingWithFaceAllowed_fingerprintLockout() { - // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException { + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN fingerprint (class 3) is lock out + fingerprintErrorTemporaryLockOut(); + + // THEN unlocking with face is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FACE)); + } + + @Test + public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException { + setupFaceAuth(/* isClass3 */ true); + setupFingerprintAuth(/* isClass3 */ true); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN fingerprint (class 3) is lock out + fingerprintErrorTemporaryLockOut(); // THEN unlocking with face is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -719,6 +766,38 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException { + setupFaceAuth(/* isClass3 */ true); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN face (class 3) is lock out + faceAuthLockOut(); + + // THEN unlocking with fingerprint is not allowed + Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test + public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException { + setupFaceAuth(/* isClass3 */ false); + setupFingerprintAuth(/* isClass3 */ true); + + // GIVEN primary auth is not required by StrongAuthTracker + primaryAuthNotRequiredByStrongAuthTracker(); + + // WHEN face (class 1) is lock out + faceAuthLockOut(); + + // THEN unlocking with fingerprint is still allowed + Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( + BiometricSourceType.FINGERPRINT)); + } + + @Test public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() { // GIVEN unlocking with biometric is not allowed when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); @@ -731,10 +810,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testUnlockingWithFpAllowed_fingerprintLockout() { // GIVEN unlocking with biometric is allowed - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // WHEN fingerprint is lock out + fingerprintErrorTemporaryLockOut(); // THEN unlocking with fingerprint is not allowed Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( @@ -757,8 +836,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null); Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); - // WHEN fingerprint is locked out - fingerprintErrorTemporaryLockedOut(); + // WHEN fingerprint is lock out + fingerprintErrorTemporaryLockOut(); // THEN user is NOT considered as "having trust" and bouncer cannot be skipped Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser())); @@ -826,7 +905,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // GIVEN udfps is supported and strong auth required for weak biometrics (face) only givenUdfpsSupported(); - strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face + primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face // WHEN the device wakes up mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -863,7 +942,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() { // GIVEN bypass is enabled, face detection is NOT supported and strong auth is required lockscreenBypassIsAllowed(); - strongAuthRequiredEncrypted(); + primaryAuthRequiredEncrypted(); keyguardIsVisible(); // WHEN the device wakes up @@ -931,6 +1010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); verifyFingerprintAuthenticateNeverCalled(); // WHEN alternate bouncer is shown + mKeyguardUpdateMonitor.setKeyguardShowing(true, true); mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); // THEN make sure FP listening begins @@ -1011,10 +1091,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() { - // test whether face will be skipped if authenticated, so the value of isStrongBiometric + // test whether face will be skipped if authenticated, so the value of isClass3Biometric // doesn't matter here mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(), - true /* isStrongBiometric */); + true /* isClass3Biometric */); setKeyguardBouncerVisibility(true); mTestableLooper.processAllMessages(); @@ -1027,7 +1107,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); keyguardIsVisible(); - faceAuthLockedOut(); + faceAuthLockOut(); verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt()); } @@ -1050,7 +1130,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); keyguardIsVisible(); - faceAuthLockedOut(); + faceAuthLockOut(); mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, ""); @@ -1060,32 +1140,32 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenFace() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @Test public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */)) + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @Test public void testGetUserCanSkipBouncer_whenFingerprint() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @Test public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() { - when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */)) + when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */)) .thenReturn(false); int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */); + mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse(); } @@ -1126,9 +1206,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @BiometricConstants.LockoutMode int fingerprintLockoutMode, @BiometricConstants.LockoutMode int faceLockoutMode) { final int newUser = 12; - final boolean faceLocked = + final boolean faceLockOut = faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; - final boolean fpLocked = + final boolean fpLockOut = fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE; mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON); @@ -1161,8 +1241,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { eq(false), eq(BiometricSourceType.FINGERPRINT)); // THEN locked out states are updated - assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked); - assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked); + assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut); + assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut); // Fingerprint should be cancelled on lockout if going to lockout state, else // restarted if it's not @@ -1443,7 +1523,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { throws RemoteException { // GIVEN SFPS supported and enrolled final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); - props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + props.add(createFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON, + /* isClass3 */ true)); when(mAuthController.getSfpsProps()).thenReturn(props); when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); @@ -1466,17 +1547,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); } - private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( - @FingerprintSensorProperties.SensorType int sensorType) { - return new FingerprintSensorPropertiesInternal( - 0 /* sensorId */, - SensorProperties.STRENGTH_STRONG, - 1 /* maxEnrollmentsPerUser */, - new ArrayList<ComponentInfoInternal>(), - sensorType, - true /* resetLockoutRequiresHardwareAuthToken */); - } - @Test public void testShouldNotListenForUdfps_whenTrustEnabled() { // GIVEN a "we should listen for udfps" state @@ -1613,7 +1683,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { keyguardNotGoingAway(); occludingAppRequestsFaceAuth(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1622,7 +1692,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Fingerprint is locked out. - fingerprintErrorTemporaryLockedOut(); + fingerprintErrorTemporaryLockOut(); assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse(); } @@ -1634,7 +1704,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { bouncerFullyVisibleAndNotGoingToSleep(); keyguardNotGoingAway(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1657,7 +1727,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { bouncerFullyVisibleAndNotGoingToSleep(); keyguardNotGoingAway(); currentUserIsPrimary(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1684,7 +1754,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // Face auth should run when the following is true. keyguardNotGoingAway(); bouncerFullyVisibleAndNotGoingToSleep(); - strongAuthNotRequired(); + primaryAuthNotRequiredByStrongAuthTracker(); biometricsEnabledForCurrentUser(); currentUserDoesNotHaveTrust(); biometricsNotDisabledThroughDevicePolicyManager(); @@ -1940,7 +2010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue(); // Face is locked out. - faceAuthLockedOut(); + faceAuthLockOut(); mTestableLooper.processAllMessages(); // This is needed beccause we want to show face locked out error message whenever face auth @@ -2578,7 +2648,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture()); // GIVEN device is locked out - fingerprintErrorTemporaryLockedOut(); + fingerprintErrorTemporaryLockOut(); // GIVEN FP detection is running givenDetectFingerprintWithClearingFingerprintManagerInvocations(); @@ -2662,6 +2732,21 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { KeyguardUpdateMonitor.getCurrentUser())).isTrue(); } + @Test + public void testFingerprintListeningStateWhenOccluded() { + when(mAuthController.isUdfpsSupported()).thenReturn(true); + + mKeyguardUpdateMonitor.setKeyguardShowing(false, false); + mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_BIOMETRIC); + mKeyguardUpdateMonitor.setKeyguardShowing(false, true); + + verifyFingerprintAuthenticateNeverCalled(); + + mKeyguardUpdateMonitor.setKeyguardShowing(true, true); + mKeyguardUpdateMonitor.setAlternateBouncerShowing(true); + + verifyFingerprintAuthenticateCall(); + } private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); @@ -2705,8 +2790,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void supportsFaceDetection() throws RemoteException { + final boolean isClass3 = !mFaceSensorProperties.isEmpty() + && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG; mFaceSensorProperties = - List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true)); + List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3)); mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties); } @@ -2734,7 +2821,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } } - private void faceAuthLockedOut() { + private void faceAuthLockOut() { mKeyguardUpdateMonitor.mFaceAuthenticationCallback .onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, ""); } @@ -2767,7 +2854,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.setSwitchingUser(true); } - private void fingerprintErrorTemporaryLockedOut() { + private void fingerprintErrorTemporaryLockOut() { mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback .onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out"); } @@ -2821,18 +2908,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { ); } - private void strongAuthRequiredEncrypted() { + private void primaryAuthRequiredEncrypted() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false); } - private void strongAuthRequiredForWeakBiometricOnly() { + private void primaryAuthRequiredForWeakBiometricOnly() { when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false); } - private void strongAuthNotRequired() { + private void primaryAuthNotRequiredByStrongAuthTracker() { when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser())) .thenReturn(0); when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true); @@ -2917,10 +3004,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } private void givenDetectFace() throws RemoteException { - // GIVEN bypass is enabled, face detection is supported and strong auth is required + // GIVEN bypass is enabled, face detection is supported and primary auth is required lockscreenBypassIsAllowed(); supportsFaceDetection(); - strongAuthRequiredEncrypted(); + primaryAuthRequiredEncrypted(); keyguardIsVisible(); // fingerprint is NOT running, UDFPS is NOT supported diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt index 64fec5bfd4ed..dea2082a2c22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.shade +package com.android.keyguard import android.animation.Animator import android.testing.AndroidTestingRunner import android.transition.TransitionValues import androidx.test.filters.SmallTest -import com.android.keyguard.KeyguardStatusViewController +import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter import com.android.systemui.SysuiTestCase -import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -33,14 +32,14 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class SplitShadeTransitionAdapterTest : SysuiTestCase() { - @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController + @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController private lateinit var adapter: SplitShadeTransitionAdapter @Before fun setUp() { MockitoAnnotations.initMocks(this) - adapter = SplitShadeTransitionAdapter(keyguardStatusViewController) + adapter = SplitShadeTransitionAdapter(KeyguardClockSwitchController) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt deleted file mode 100644 index 44da5f42aafd..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui - -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.content.res.Resources -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.kotlinArgumentCaptor -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import java.io.File -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.MockitoAnnotations - -@RunWith(AndroidTestingRunner::class) -@SmallTest -class ChooserPinMigrationTest : SysuiTestCase() { - - private val fakeFeatureFlags = FakeFeatureFlags() - private val fakePreferences = - mutableMapOf( - "TestPinnedPackage/TestPinnedClass" to true, - "TestUnpinnedPackage/TestUnpinnedClass" to false, - ) - private val intent = kotlinArgumentCaptor<Intent>() - private val permission = kotlinArgumentCaptor<String>() - - private lateinit var chooserPinMigration: ChooserPinMigration - - @Mock private lateinit var mockContext: Context - @Mock private lateinit var mockResources: Resources - @Mock - private lateinit var mockLegacyPinPrefsFileSupplier: - ChooserPinMigration.Companion.LegacyPinPrefsFileSupplier - @Mock private lateinit var mockFile: File - @Mock private lateinit var mockSharedPreferences: SharedPreferences - @Mock private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor - @Mock private lateinit var mockBroadcastSender: BroadcastSender - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - - whenever(mockContext.resources).thenReturn(mockResources) - whenever(mockContext.getSharedPreferences(any<File>(), anyInt())) - .thenReturn(mockSharedPreferences) - whenever(mockResources.getString(anyInt())).thenReturn("TestPackage/TestClass") - whenever(mockSharedPreferences.all).thenReturn(fakePreferences) - whenever(mockSharedPreferences.edit()).thenReturn(mockSharedPreferencesEditor) - whenever(mockSharedPreferencesEditor.commit()).thenReturn(true) - whenever(mockLegacyPinPrefsFileSupplier.get()).thenReturn(mockFile) - whenever(mockFile.exists()).thenReturn(true) - whenever(mockFile.delete()).thenReturn(true) - fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, true) - } - - @Test - fun start_performsMigration() { - // Arrange - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture()) - assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION") - assertThat(intent.value.`package`).isEqualTo("TestPackage") - assertThat(intent.value.extras?.keySet()).hasSize(2) - assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false)) - .isTrue() - assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true)) - .isFalse() - assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION") - - // Assert - verify(mockSharedPreferencesEditor).clear() - verify(mockSharedPreferencesEditor).commit() - - // Assert - verify(mockFile).delete() - } - - @Test - fun start_doesNotDeleteLegacyPreferencesFile_whenClearingItFails() { - // Arrange - whenever(mockSharedPreferencesEditor.commit()).thenReturn(false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture()) - assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION") - assertThat(intent.value.`package`).isEqualTo("TestPackage") - assertThat(intent.value.extras?.keySet()).hasSize(2) - assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false)) - .isTrue() - assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue() - assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true)) - .isFalse() - assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION") - - // Assert - verify(mockSharedPreferencesEditor).clear() - verify(mockSharedPreferencesEditor).commit() - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_OnlyDeletesLegacyPreferencesFile_whenEmpty() { - // Arrange - whenever(mockSharedPreferences.all).thenReturn(emptyMap()) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile).delete() - } - - @Test - fun start_DoesNotDoMigration_whenFlagIsDisabled() { - // Arrange - fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_DoesNotDoMigration_whenLegacyPreferenceFileNotPresent() { - // Arrange - whenever(mockFile.exists()).thenReturn(false) - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } - - @Test - fun start_DoesNotDoMigration_whenConfiguredChooserComponentIsInvalid() { - // Arrange - whenever(mockResources.getString(anyInt())).thenReturn("InvalidComponent") - chooserPinMigration = - ChooserPinMigration( - mockContext, - fakeFeatureFlags, - mockBroadcastSender, - mockLegacyPinPrefsFileSupplier, - ) - - // Act - chooserPinMigration.start() - - // Assert - verifyZeroInteractions(mockBroadcastSender) - - // Assert - verifyZeroInteractions(mockSharedPreferencesEditor) - - // Assert - verify(mockFile, never()).delete() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt new file mode 100644 index 000000000000..01d3a3931052 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt @@ -0,0 +1,119 @@ +/* + * 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 + +import android.graphics.Point +import android.hardware.display.DisplayManagerGlobal +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.Display +import android.view.DisplayAdjustments +import android.view.DisplayInfo +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.biometrics.AuthController +import com.android.systemui.decor.FaceScanningProviderFactory +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.log.ScreenDecorationsLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@RunWithLooper +@RunWith(AndroidTestingRunner::class) +@SmallTest +class FaceScanningProviderFactoryTest : SysuiTestCase() { + + private lateinit var underTest: FaceScanningProviderFactory + + @Mock private lateinit var authController: AuthController + + @Mock private lateinit var statusBarStateController: StatusBarStateController + + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Mock private lateinit var display: Display + + private val displayId = 2 + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + val displayInfo = DisplayInfo() + val dmGlobal = mock(DisplayManagerGlobal::class.java) + val display = + Display( + dmGlobal, + displayId, + displayInfo, + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS + ) + whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo) + val displayContext = context.createDisplayContext(display) as SysuiTestableContext + displayContext.orCreateTestableResources.addOverride( + R.array.config_displayUniqueIdArray, + arrayOf(displayId) + ) + displayContext.orCreateTestableResources.addOverride( + R.bool.config_fillMainBuiltInDisplayCutout, + true + ) + underTest = + FaceScanningProviderFactory( + authController, + displayContext, + statusBarStateController, + keyguardUpdateMonitor, + mock(Executor::class.java), + ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")) + ) + + whenever(authController.faceSensorLocation).thenReturn(Point(10, 10)) + } + + @Test + fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false) + whenever(authController.isShowing).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isFalse() + } + + @Test + fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(authController.isShowing).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() + } + + @Test + fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() { + whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true) + whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true) + + assertThat(underTest.shouldShowFaceScanningAnim()).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index edee3f1b9f02..64c028e6a095 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1017,7 +1017,7 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN the display should be unconfigured once. If the timeout action is not // cancelled, the display would be unconfigured twice which would cause two // FP attempts. - verify(mUdfpsView, times(1)).unconfigureDisplay(); + verify(mUdfpsView).unconfigureDisplay(); } else { verify(mUdfpsView, never()).unconfigureDisplay(); } @@ -1301,8 +1301,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mBiometricExecutor.runAllReady(); downEvent.recycle(); - // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor) - verify(mInputManager, times(2)).pilferPointers(any()); + // THEN the touch is pilfered + verify(mInputManager).pilferPointers(any()); } @Test @@ -1340,7 +1340,7 @@ public class UdfpsControllerTest extends SysuiTestCase { downEvent.recycle(); // THEN the touch is NOT pilfered - verify(mInputManager, times(0)).pilferPointers(any()); + verify(mInputManager, never()).pilferPointers(any()); } @Test @@ -1380,7 +1380,51 @@ public class UdfpsControllerTest extends SysuiTestCase { downEvent.recycle(); // THEN the touch is pilfered - verify(mInputManager, times(1)).pilferPointers(any()); + verify(mInputManager).pilferPointers(any()); + } + + @Test + public void onTouch_withNewTouchDetection_doNotPilferWhenPullingUpBouncer() + throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultMove = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_MOVE event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS + when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); + when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f); + + // WHEN ACTION_MOVE is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultMove); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricExecutor.runAllReady(); + moveEvent.recycle(); + + // THEN the touch is NOT pilfered + verify(mInputManager, never()).pilferPointers(any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index 8cb91304808d..4cb99a23a531 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -120,7 +120,6 @@ public class BrightLineClassifierTest extends SysuiTestCase { gestureCompleteListenerCaptor.capture()); mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue(); - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true); mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } @@ -260,13 +259,6 @@ public class BrightLineClassifierTest extends SysuiTestCase { } @Test - public void testIsFalseLongTap_FalseLongTap_NotFlagged() { - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false); - when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); - assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse(); - } - - @Test public void testIsFalseLongTap_FalseLongTap() { when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult); assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 315774aad71a..292fdff0027d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -94,7 +94,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, mAccessibilityManager, false, mFakeFeatureFlags); - mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true); mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java index fd6e31ba3bee..18515825967f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.clipboardoverlay; -import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT; - import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE; import static org.junit.Assert.assertEquals; @@ -40,7 +38,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; -import com.android.systemui.flags.FakeFeatureFlags; import org.junit.Before; import org.junit.Test; @@ -65,7 +62,6 @@ public class ClipboardListenerTest extends SysuiTestCase { private ClipboardOverlayController mOverlayController; @Mock private ClipboardToast mClipboardToast; - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private UiEventLogger mUiEventLogger; @@ -99,10 +95,8 @@ public class ClipboardListenerTest extends SysuiTestCase { when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData); when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource); - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); - mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider, - mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger); + mClipboardToast, mClipboardManager, mUiEventLogger); } @@ -222,34 +216,4 @@ public class ClipboardListenerTest extends SysuiTestCase { verify(mClipboardToast, times(1)).showCopiedToast(); verifyZeroInteractions(mOverlayControllerProvider); } - - @Test - public void test_minimizedLayoutFlagOff_usesLegacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - - mClipboardListener.start(); - mClipboardListener.onPrimaryClipChanged(); - - verify(mOverlayControllerProvider).get(); - - verify(mOverlayController).setClipDataLegacy( - mClipDataCaptor.capture(), mStringCaptor.capture()); - - assertEquals(mSampleClipData, mClipDataCaptor.getValue()); - assertEquals(mSampleSource, mStringCaptor.getValue()); - } - - @Test - public void test_minimizedLayoutFlagOn_usesNew() { - mClipboardListener.start(); - mClipboardListener.onPrimaryClipChanged(); - - verify(mOverlayControllerProvider).get(); - - verify(mOverlayController).setClipData( - mClipDataCaptor.capture(), mStringCaptor.capture()); - - assertEquals(mSampleClipData, mClipDataCaptor.getValue()); - assertEquals(mSampleSource, mStringCaptor.getValue()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 299869c04d34..8600b7c48d33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -25,7 +25,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; -import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT; import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; import static org.mockito.ArgumentMatchers.any; @@ -123,7 +122,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { new ClipData.Item("Test Item")); mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false); - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests mOverlayController = new ClipboardOverlayController( mContext, @@ -146,178 +144,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test - public void test_setClipData_invalidImageData_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - ClipData clipData = new ClipData("", new String[]{"image/png"}, - new ClipData.Item(Uri.parse(""))); - - mOverlayController.setClipDataLegacy(clipData, ""); - - verify(mClipboardOverlayView, times(1)).showDefaultTextPreview(); - verify(mClipboardOverlayView, times(1)).showShareChip(); - verify(mClipboardOverlayView, times(1)).getEnterAnimation(); - } - - @Test - public void test_setClipData_nonImageUri_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - ClipData clipData = new ClipData("", new String[]{"resource/png"}, - new ClipData.Item(Uri.parse(""))); - - mOverlayController.setClipDataLegacy(clipData, ""); - - verify(mClipboardOverlayView, times(1)).showDefaultTextPreview(); - verify(mClipboardOverlayView, times(1)).showShareChip(); - verify(mClipboardOverlayView, times(1)).getEnterAnimation(); - } - - @Test - public void test_setClipData_textData_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false); - verify(mClipboardOverlayView, times(1)).showShareChip(); - verify(mClipboardOverlayView, times(1)).getEnterAnimation(); - } - - @Test - public void test_setClipData_sensitiveTextData_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - - ClipDescription description = mSampleClipData.getDescription(); - PersistableBundle b = new PersistableBundle(); - b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true); - description.setExtras(b); - ClipData data = new ClipData(description, mSampleClipData.getItemAt(0)); - mOverlayController.setClipDataLegacy(data, ""); - - verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true); - verify(mClipboardOverlayView, times(1)).showShareChip(); - verify(mClipboardOverlayView, times(1)).getEnterAnimation(); - } - - @Test - public void test_setClipData_repeatedCalls_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - when(mAnimator.isRunning()).thenReturn(true); - - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - verify(mClipboardOverlayView, times(1)).getEnterAnimation(); - } - - @Test - public void test_viewCallbacks_onShareTapped_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - mCallbacks.onShareButtonTapped(); - - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, ""); - verify(mClipboardOverlayView, times(1)).getExitAnimation(); - } - - @Test - public void test_viewCallbacks_onDismissTapped_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - mCallbacks.onDismissButtonTapped(); - - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, ""); - verify(mClipboardOverlayView, times(1)).getExitAnimation(); - } - - @Test - public void test_multipleDismissals_dismissesOnce_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - - mCallbacks.onSwipeDismissInitiated(mAnimator); - mCallbacks.onDismissButtonTapped(); - mCallbacks.onSwipeDismissInitiated(mAnimator); - mCallbacks.onDismissButtonTapped(); - - verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null); - verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); - } - - @Test - public void test_remoteCopy_withFlagOn_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); - when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); - - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - verify(mTimeoutHandler, never()).resetTimeout(); - } - - @Test - public void test_remoteCopy_withFlagOff_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); - - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - verify(mTimeoutHandler).resetTimeout(); - } - - @Test - public void test_nonRemoteCopy_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); - when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false); - - mOverlayController.setClipDataLegacy(mSampleClipData, ""); - - verify(mTimeoutHandler).resetTimeout(); - } - - @Test - public void test_logsUseLastClipSource_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - - mOverlayController.setClipDataLegacy(mSampleClipData, "first.package"); - mCallbacks.onDismissButtonTapped(); - mOverlayController.setClipDataLegacy(mSampleClipData, "second.package"); - mCallbacks.onDismissButtonTapped(); - - verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package"); - verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package"); - verifyNoMoreInteractions(mUiEventLogger); - } - - @Test - public void test_logOnClipboardActionsShown_legacy() { - mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false); - ClipData.Item item = mSampleClipData.getItemAt(0); - item.setTextLinks(Mockito.mock(TextLinks.class)); - mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); - when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString())) - .thenReturn(true); - when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString())) - .thenReturn(Optional.of(Mockito.mock(RemoteAction.class))); - when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - ((Runnable) invocation.getArgument(0)).run(); - return null; - } - }); - - mOverlayController.setClipDataLegacy( - new ClipData(mSampleClipData.getDescription(), item), "actionShownSource"); - mExecutor.runAllReady(); - - verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource"); - verifyNoMoreInteractions(mUiEventLogger); - } - - // start of refactored setClipData tests - @Test public void test_setClipData_invalidImageData() { ClipData clipData = new ClipData("", new String[]{"image/png"}, new ClipData.Item(Uri.parse(""))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java index 3d8f04e08825..673b5eb5dc3c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java @@ -142,81 +142,6 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase { assertEquals(actionB, result); } - // TODO(b/267162944): Next four tests (marked "legacy") are obsolete once - // CLIPBOARD_MINIMIZED_LAYOUT flag is released and removed - @Test - public void test_getAction_noLinks_returnsEmptyOptional_legacy() { - ClipData.Item item = new ClipData.Item("no text links"); - item.setTextLinks(Mockito.mock(TextLinks.class)); - - Optional<RemoteAction> action = mClipboardUtils.getAction(item, ""); - - assertTrue(action.isEmpty()); - } - - @Test - public void test_getAction_returnsFirstLink_legacy() { - when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build()); - when(mClipDataItem.getText()).thenReturn(""); - RemoteAction actionA = constructRemoteAction("abc"); - RemoteAction actionB = constructRemoteAction("def"); - TextClassification classificationA = Mockito.mock(TextClassification.class); - when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA)); - TextClassification classificationB = Mockito.mock(TextClassification.class); - when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB)); - when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn( - classificationA, classificationB); - - RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null); - - assertEquals(actionA, result); - } - - @Test - public void test_getAction_skipsMatchingComponent_legacy() { - when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build()); - when(mClipDataItem.getText()).thenReturn(""); - RemoteAction actionA = constructRemoteAction("abc"); - RemoteAction actionB = constructRemoteAction("def"); - TextClassification classificationA = Mockito.mock(TextClassification.class); - when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA)); - TextClassification classificationB = Mockito.mock(TextClassification.class); - when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB)); - when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn( - classificationA, classificationB); - - RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "abc").orElse(null); - - assertEquals(actionB, result); - } - - @Test - public void test_getAction_skipsShortEntity_legacy() { - TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22"); - final Map<String, Float> scores = new ArrayMap<>(); - scores.put(TextClassifier.TYPE_EMAIL, 1f); - textLinks.addLink(20, 22, scores); - textLinks.addLink(0, 22, scores); - - when(mClipDataItem.getTextLinks()).thenReturn(textLinks.build()); - when(mClipDataItem.getText()).thenReturn(textLinks.build().getText()); - - RemoteAction actionA = constructRemoteAction("abc"); - RemoteAction actionB = constructRemoteAction("def"); - TextClassification classificationA = Mockito.mock(TextClassification.class); - when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA)); - TextClassification classificationB = Mockito.mock(TextClassification.class); - when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB)); - when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn( - classificationA); - when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn( - classificationB); - - RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null); - - assertEquals(actionB, result); - } - @Test public void test_extra_withPackage_returnsTrue() { PersistableBundle b = new PersistableBundle(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java index 5fcf414f0251..8fdc4918b9fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java @@ -18,8 +18,8 @@ package com.android.systemui.dreams.complication; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -29,23 +29,45 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import java.util.Collection; import java.util.HashSet; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class ComplicationCollectionLiveDataTest extends SysuiTestCase { + + private FakeExecutor mExecutor; + private DreamOverlayStateController mStateController; + private ComplicationCollectionLiveData mLiveData; + private FakeFeatureFlags mFeatureFlags; + @Mock + private Observer mObserver; + @Before - public void setUp() throws Exception { - allowTestableLooperAsMainThread(); + public void setUp() { + MockitoAnnotations.initMocks(this); + mFeatureFlags = new FakeFeatureFlags(); + mExecutor = new FakeExecutor(new FakeSystemClock()); + mFeatureFlags.set(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS, true); + mStateController = new DreamOverlayStateController( + mExecutor, + /* overlayEnabled= */ true, + mFeatureFlags); + mLiveData = new ComplicationCollectionLiveData(mStateController); } @Test @@ -53,45 +75,41 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase { * Ensures registration and callback lifecycles are respected. */ public void testLifecycle() { - getContext().getMainExecutor().execute(() -> { - final DreamOverlayStateController stateController = - Mockito.mock(DreamOverlayStateController.class); - final ComplicationCollectionLiveData liveData = - new ComplicationCollectionLiveData(stateController); - final HashSet<Complication> complications = new HashSet<>(); - final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class); - complications.add(Mockito.mock(Complication.class)); - - when(stateController.getComplications()).thenReturn(complications); - - liveData.observeForever(observer); - ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor = - ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class); - - verify(stateController).addCallback(callbackCaptor.capture()); - verifyUpdate(observer, complications); - - complications.add(Mockito.mock(Complication.class)); - callbackCaptor.getValue().onComplicationsChanged(); - - verifyUpdate(observer, complications); - - callbackCaptor.getValue().onAvailableComplicationTypesChanged(); - - verifyUpdate(observer, complications); - }); + final HashSet<Complication> complications = new HashSet<>(); + mLiveData.observeForever(mObserver); + mExecutor.runAllReady(); + // Verify observer called with empty complications + assertObserverCalledWith(complications); + + addComplication(mock(Complication.class), complications); + assertObserverCalledWith(complications); + + addComplication(mock(Complication.class), complications); + assertObserverCalledWith(complications); + + mStateController.setAvailableComplicationTypes(0); + mExecutor.runAllReady(); + assertObserverCalledWith(complications); + mLiveData.removeObserver(mObserver); } - void verifyUpdate(Observer<Collection<Complication>> observer, - Collection<Complication> targetCollection) { + private void assertObserverCalledWith(Collection<Complication> targetCollection) { ArgumentCaptor<Collection<Complication>> collectionCaptor = ArgumentCaptor.forClass(Collection.class); - verify(observer).onChanged(collectionCaptor.capture()); + verify(mObserver).onChanged(collectionCaptor.capture()); + + final Collection<Complication> collection = collectionCaptor.getValue(); - final Collection collection = collectionCaptor.getValue(); assertThat(collection.containsAll(targetCollection) && targetCollection.containsAll(collection)).isTrue(); - Mockito.clearInvocations(observer); + Mockito.clearInvocations(mObserver); + } + + private void addComplication(Complication complication, + Collection<Complication> complications) { + complications.add(complication); + mStateController.addComplication(complication); + mExecutor.runAllReady(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index c93e677071cd..0de9608b906f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -66,6 +66,8 @@ import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -142,6 +144,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock CentralSurfaces mCentralSurfaces; + private FakeFeatureFlags mFeatureFlags; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -160,6 +164,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mColorExtractor, mDumpManager, mKeyguardStateController, mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager, mShadeWindowLogger); + mFeatureFlags = new FakeFeatureFlags(); + DejankUtils.setImmediate(true); @@ -515,6 +521,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean()); } + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testNotStartingKeyguardWhenFlagIsDisabled() { + mViewMediator.setShowingLocked(false); + when(mKeyguardStateController.isShowing()).thenReturn(false); + + mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, false); + mViewMediator.onDreamingStarted(); + assertFalse(mViewMediator.isShowingAndNotOccluded()); + } + + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testStartingKeyguardWhenFlagIsEnabled() { + mViewMediator.setShowingLocked(true); + when(mKeyguardStateController.isShowing()).thenReturn(true); + + mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, true); + mViewMediator.onDreamingStarted(); + assertTrue(mViewMediator.isShowingAndNotOccluded()); + } + private void createAndStartViewMediator() { mViewMediator = new KeyguardViewMediator( mContext, @@ -545,7 +573,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mShadeController, () -> mNotificationShadeWindowController, () -> mActivityLaunchAnimator, - () -> mScrimController); + () -> mScrimController, + mFeatureFlags); mViewMediator.start(); mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 86e8c9accc45..a668af340e7b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat +import java.util.Locale import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope @@ -67,6 +68,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { @Before fun setUp() { + context.resources.configuration.setLayoutDirection(Locale.US) config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1) config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2) val testDispatcher = StandardTestDispatcher() @@ -222,6 +224,40 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { } @Test + fun getSlotPickerRepresentations_rightToLeft_slotsReversed() { + context.resources.configuration.setLayoutDirection(Locale("he", "IL")) + val slot1 = "slot1" + val slot2 = "slot2" + val slot3 = "slot3" + context.orCreateTestableResources.addOverride( + R.array.config_keyguardQuickAffordanceSlots, + arrayOf( + "$slot1:2", + "$slot2:4", + "$slot3:5", + ), + ) + + assertThat(underTest.getSlotPickerRepresentations()) + .isEqualTo( + listOf( + KeyguardSlotPickerRepresentation( + id = slot3, + maxSelectedAffordances = 5, + ), + KeyguardSlotPickerRepresentation( + id = slot2, + maxSelectedAffordances = 4, + ), + KeyguardSlotPickerRepresentation( + id = slot1, + maxSelectedAffordances = 2, + ), + ) + ) + } + + @Test fun `selections for secondary user`() = testScope.runTest { userTracker.set( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index 51988ef1ab78..77bb12c2cbda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -29,20 +29,20 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.util.mockito.any +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -51,8 +51,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class KeyguardLongPressInteractorTest : SysuiTestCase() { - @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var logger: UiEventLogger + @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper private lateinit var underTest: KeyguardLongPressInteractor @@ -63,6 +63,14 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer { + it.arguments[0] + } + + testScope = TestScope() + keyguardRepository = FakeKeyguardRepository() + keyguardTransitionRepository = FakeKeyguardTransitionRepository() + runBlocking { createUnderTest() } } @@ -98,60 +106,117 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { } @Test - fun `long-pressed - pop-up clicked - starts activity`() = + fun longPressed_menuClicked_showsSettings() = testScope.runTest { - val menu = collectLastValue(underTest.menu) + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) runCurrent() - val x = 100 - val y = 123 - underTest.onLongPress(x, y) - assertThat(menu()).isNotNull() - assertThat(menu()?.position?.x).isEqualTo(x) - assertThat(menu()?.position?.y).isEqualTo(y) + underTest.onLongPress() + assertThat(isMenuVisible).isTrue() + + underTest.onMenuTouchGestureEnded(/* isClick= */ true) + + assertThat(isMenuVisible).isFalse() + assertThat(shouldOpenSettings).isTrue() + } + + @Test + fun onSettingsShown_consumesSettingsShowEvent() = + testScope.runTest { + val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) + runCurrent() - menu()?.onClicked?.invoke() + underTest.onLongPress() + underTest.onMenuTouchGestureEnded(/* isClick= */ true) + assertThat(shouldOpenSettings).isTrue() - assertThat(menu()).isNull() - verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean()) + underTest.onSettingsShown() + assertThat(shouldOpenSettings).isFalse() } @Test - fun `long-pressed - pop-up dismissed - never starts activity`() = + fun onTouchedOutside_neverShowsSettings() = testScope.runTest { - val menu = collectLastValue(underTest.menu) + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) runCurrent() - menu()?.onDismissed?.invoke() + underTest.onTouchedOutside() - assertThat(menu()).isNull() - verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean()) + assertThat(isMenuVisible).isFalse() + assertThat(shouldOpenSettings).isFalse() + } + + @Test + fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() = + testScope.runTest { + createUnderTest(isOpenWppDirectlyEnabled = true) + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings) + runCurrent() + + underTest.onLongPress() + + assertThat(isMenuVisible).isFalse() + assertThat(shouldOpenSettings).isTrue() } - @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI. @Test fun `long pressed - close dialogs broadcast received - popup dismissed`() = testScope.runTest { - val menu = collectLastValue(underTest.menu) + val isMenuVisible by collectLastValue(underTest.isMenuVisible) runCurrent() - underTest.onLongPress(123, 456) - assertThat(menu()).isNotNull() + underTest.onLongPress() + assertThat(isMenuVisible).isTrue() fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver -> broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) } - assertThat(menu()).isNull() + assertThat(isMenuVisible).isFalse() + } + + @Test + fun closesDialogAfterTimeout() = + testScope.runTest { + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + runCurrent() + + underTest.onLongPress() + assertThat(isMenuVisible).isTrue() + + advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + + assertThat(isMenuVisible).isFalse() + } + + @Test + fun closesDialogAfterTimeout_onlyAfterTouchGestureEnded() = + testScope.runTest { + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + runCurrent() + + underTest.onLongPress() + assertThat(isMenuVisible).isTrue() + underTest.onMenuTouchGestureStarted() + + advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + assertThat(isMenuVisible).isTrue() + + underTest.onMenuTouchGestureEnded(/* isClick= */ false) + advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS) + assertThat(isMenuVisible).isFalse() } @Test fun `logs when menu is shown`() = testScope.runTest { - collectLastValue(underTest.menu) + collectLastValue(underTest.isMenuVisible) runCurrent() - underTest.onLongPress(100, 123) + underTest.onLongPress() verify(logger) .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN) @@ -160,41 +225,61 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { @Test fun `logs when menu is clicked`() = testScope.runTest { - val menu = collectLastValue(underTest.menu) + collectLastValue(underTest.isMenuVisible) runCurrent() - underTest.onLongPress(100, 123) - menu()?.onClicked?.invoke() + underTest.onLongPress() + underTest.onMenuTouchGestureEnded(/* isClick= */ true) verify(logger) .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED) } + @Test + fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() = + testScope.runTest { + val isMenuVisible by collectLastValue(underTest.isMenuVisible) + runCurrent() + underTest.onLongPress() + assertThat(isMenuVisible).isTrue() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + to = KeyguardState.GONE, + ), + ) + assertThat(isMenuVisible).isFalse() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + to = KeyguardState.LOCKSCREEN, + ), + ) + assertThat(isMenuVisible).isFalse() + } + private suspend fun createUnderTest( isLongPressFeatureEnabled: Boolean = true, isRevampedWppFeatureEnabled: Boolean = true, + isOpenWppDirectlyEnabled: Boolean = false, ) { - testScope = TestScope() - keyguardRepository = FakeKeyguardRepository() - keyguardTransitionRepository = FakeKeyguardTransitionRepository() - underTest = KeyguardLongPressInteractor( - unsafeContext = context, scope = testScope.backgroundScope, transitionInteractor = KeyguardTransitionInteractor( repository = keyguardTransitionRepository, ), repository = keyguardRepository, - activityStarter = activityStarter, logger = logger, featureFlags = FakeFeatureFlags().apply { set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled) set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled) + set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled) }, broadcastDispatcher = fakeBroadcastDispatcher, + accessibilityManager = accessibilityManager ) setUpState() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index bfc09d7c0379..224eec1bf3ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -20,10 +20,12 @@ import android.app.admin.DevicePolicyManager import android.content.Intent import android.os.UserHandle import androidx.test.filters.SmallTest +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper @@ -38,10 +40,13 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -51,6 +56,7 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any @@ -91,6 +97,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Mock private lateinit var commandQueue: CommandQueue @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper private lateinit var underTest: KeyguardBottomAreaViewModel @@ -134,6 +142,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { FakeFeatureFlags().apply { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.FACE_AUTH_REFACTOR, true) + set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false) + set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false) } val keyguardInteractor = @@ -196,6 +206,19 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) + val keyguardLongPressInteractor = + KeyguardLongPressInteractor( + scope = testScope.backgroundScope, + transitionInteractor = + KeyguardTransitionInteractor( + repository = FakeKeyguardTransitionRepository(), + ), + repository = repository, + logger = UiEventLoggerFake(), + featureFlags = featureFlags, + broadcastDispatcher = broadcastDispatcher, + accessibilityManager = accessibilityManager, + ) underTest = KeyguardBottomAreaViewModel( keyguardInteractor = keyguardInteractor, @@ -216,6 +239,14 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ), bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), burnInHelperWrapper = burnInHelperWrapper, + longPressViewModel = + KeyguardLongPressViewModel( + interactor = keyguardLongPressInteractor, + ), + settingsMenuViewModel = + KeyguardSettingsMenuViewModel( + interactor = keyguardLongPressInteractor, + ), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt index 543875dc31cf..1e465c791795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color @@ -42,6 +43,7 @@ import android.os.Bundle import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.util.TypedValue import android.view.View import android.view.ViewGroup import android.view.animation.Interpolator @@ -2199,7 +2201,7 @@ public class MediaControlPanelTest : SysuiTestCase() { } @Test - fun bindRecommendation_carouselNotFitThreeRecs() { + fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() { useRealConstraintSets() setupUpdatedRecommendationViewHolder() val albumArt = getColorIcon(Color.RED) @@ -2227,16 +2229,84 @@ public class MediaControlPanelTest : SysuiTestCase() { // set the screen width less than the width of media controls. player.context.resources.configuration.screenWidthDp = 350 + player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT player.attachRecommendation(recommendationViewHolder) player.bindRecommendation(data) - assertThat(player.numberOfFittedRecommendations).isEqualTo(2) - assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE) - assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE) - assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE) + val res = player.context.resources + val displayAvailableWidth = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() + val recCoverWidth: Int = + (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) + val numOfRecs = displayAvailableWidth / recCoverWidth + + assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) + recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> + if (index < numOfRecs) { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(container.id)) + .isEqualTo(ConstraintSet.VISIBLE) + } else { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + } + } + } + + @Test + fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() { + useRealConstraintSets() + setupUpdatedRecommendationViewHolder() + val albumArt = getColorIcon(Color.RED) + val data = + smartspaceData.copy( + recommendations = + listOf( + SmartspaceAction.Builder("id1", "title1") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id2", "title2") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build(), + SmartspaceAction.Builder("id3", "title3") + .setSubtitle("subtitle1") + .setIcon(albumArt) + .setExtras(Bundle.EMPTY) + .build() + ) + ) + + // set the screen width less than the width of media controls. + // We should have dp width less than 378 to test. In landscape we should have 2x. + player.context.resources.configuration.screenWidthDp = 700 + player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE + player.attachRecommendation(recommendationViewHolder) + player.bindRecommendation(data) + + val res = player.context.resources + val displayAvailableWidth = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt() + val recCoverWidth: Int = + (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) + + res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2) + val numOfRecs = displayAvailableWidth / recCoverWidth + + assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs) + recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container -> + if (index < numOfRecs) { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE) + assertThat(collapsedSet.getVisibility(container.id)) + .isEqualTo(ConstraintSet.VISIBLE) + } else { + assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE) + } + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt index c96853d1a406..a0c376ff1a1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import org.junit.After @@ -38,6 +39,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -86,15 +88,28 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() { @Test fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() { + val mainUserHandle: UserHandle = mainUser.userHandle userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1) whenever(userManager.isManagedProfile).thenReturn(true) + whenever(userManager.mainUser).thenReturn(mainUserHandle) activityRule.launchActivity(/* startIntent= */ null) - val mainUserHandle: UserHandle = mainUser.userHandle verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle)) } + @Test + fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() { + userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1) + whenever(userManager.isManagedProfile).thenReturn(true) + whenever(userManager.mainUser).thenReturn(null) + + activityRule.launchActivity(/* startIntent= */ null) + + verify(noteTaskController, never()).showNoteTask(any()) + verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any()) + } + private companion object { val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN) val workProfileUser = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 34d2b14d46a9..aa92177e863e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -51,8 +51,10 @@ import com.android.internal.util.CollectionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; -import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.nano.SystemUIProtoDump; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -62,7 +64,6 @@ import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.CustomTileStatePersister; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServiceKey; -import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserFileManager; @@ -110,8 +111,6 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private Provider<AutoTileManager> mAutoTiles; @Mock - private DumpManager mDumpManager; - @Mock private CentralSurfaces mCentralSurfaces; @Mock private QSLogger mQSLogger; @@ -125,10 +124,6 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private CustomTileStatePersister mCustomTileStatePersister; @Mock - private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder; - @Mock - private TileServiceRequestController mTileServiceRequestController; - @Mock private TileLifecycleManager.Factory mTileLifecycleManagerFactory; @Mock private TileLifecycleManager mTileLifecycleManager; @@ -137,6 +132,8 @@ public class QSTileHostTest extends SysuiTestCase { private SparseArray<SharedPreferences> mSharedPreferencesByUser; + private FakeFeatureFlags mFeatureFlags; + private FakeExecutor mMainExecutor; private QSTileHost mQSTileHost; @@ -144,12 +141,13 @@ public class QSTileHostTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mFeatureFlags = new FakeFeatureFlags(); + + mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false); + mMainExecutor = new FakeExecutor(new FakeSystemClock()); mSharedPreferencesByUser = new SparseArray<>(); - - when(mTileServiceRequestControllerBuilder.create(any())) - .thenReturn(mTileServiceRequestController); when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class))) .thenReturn(mTileLifecycleManager); when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) @@ -165,10 +163,9 @@ public class QSTileHostTest extends SysuiTestCase { mSecureSettings = new FakeSettings(); saveSetting(""); mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor, - mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces, + mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces, mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, - mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory, - mUserFileManager); + mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags); mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) { @Override @@ -686,18 +683,16 @@ public class QSTileHostTest extends SysuiTestCase { TestQSTileHost(Context context, QSFactory defaultFactory, Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, - Provider<AutoTileManager> autoTiles, DumpManager dumpManager, + Provider<AutoTileManager> autoTiles, CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, - TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileLifecycleManager.Factory tileLifecycleManagerFactory, - UserFileManager userFileManager) { + UserFileManager userFileManager, FeatureFlags featureFlags) { super(context, defaultFactory, mainExecutor, pluginManager, - tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger, + tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger, uiEventLogger, userTracker, secureSettings, customTileStatePersister, - tileServiceRequestControllerBuilder, tileLifecycleManagerFactory, - userFileManager); + tileLifecycleManagerFactory, userFileManager, featureFlags); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt index c03849b35f54..50a8d2630d79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt @@ -170,6 +170,21 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test + fun addTileAtPosition_tooLarge_addedAtEnd() = + testScope.runTest { + val tiles by collectLastValue(underTest.tilesSpecs(0)) + + val specs = "a,custom(b/c)" + storeTilesForUser(specs, 0) + + underTest.addTile(userId = 0, TileSpec.create("d"), position = 100) + + val expected = "a,custom(b/c),d" + assertThat(loadTilesForUser(0)).isEqualTo(expected) + assertThat(tiles).isEqualTo(expected.toTileSpecs()) + } + + @Test fun addTileForOtherUser_addedInThatUser() = testScope.runTest { val tilesUser0 by collectLastValue(underTest.tilesSpecs(0)) @@ -187,27 +202,27 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test - fun removeTile() = + fun removeTiles() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) storeTilesForUser("a,b", 0) - underTest.removeTile(userId = 0, TileSpec.create("a")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) assertThat(loadTilesForUser(0)).isEqualTo("b") assertThat(tiles).isEqualTo("b".toTileSpecs()) } @Test - fun removeTileNotThere_noop() = + fun removeTilesNotThere_noop() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) val specs = "a,b" storeTilesForUser(specs, 0) - underTest.removeTile(userId = 0, TileSpec.create("c")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(tiles).isEqualTo(specs.toTileSpecs()) @@ -221,7 +236,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { val specs = "a,b" storeTilesForUser(specs, 0) - underTest.removeTile(userId = 0, TileSpec.Invalid) + underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid)) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(tiles).isEqualTo(specs.toTileSpecs()) @@ -237,7 +252,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { storeTilesForUser(specs, 0) storeTilesForUser(specs, 1) - underTest.removeTile(userId = 1, TileSpec.create("a")) + underTest.removeTiles(userId = 1, listOf(TileSpec.create("a"))) assertThat(loadTilesForUser(0)).isEqualTo(specs) assertThat(user0Tiles).isEqualTo(specs.toTileSpecs()) @@ -246,6 +261,19 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { } @Test + fun removeMultipleTiles() = + testScope.runTest { + val tiles by collectLastValue(underTest.tilesSpecs(0)) + + storeTilesForUser("a,b,c,d", 0) + + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c"))) + + assertThat(loadTilesForUser(0)).isEqualTo("b,d") + assertThat(tiles).isEqualTo("b,d".toTileSpecs()) + } + + @Test fun changeTiles() = testScope.runTest { val tiles by collectLastValue(underTest.tilesSpecs(0)) @@ -310,8 +338,8 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { storeTilesForUser(specs, 0) coroutineScope { - underTest.removeTile(userId = 0, TileSpec.create("c")) - underTest.removeTile(userId = 0, TileSpec.create("a")) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("c"))) + underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"))) } assertThat(loadTilesForUser(0)).isEqualTo("b") diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt new file mode 100644 index 000000000000..7ecb4dc9376a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.os.UserHandle +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dump.nano.SystemUIProtoDump +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTile.BooleanState +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.external.CustomTile +import com.android.systemui.qs.external.CustomTileStatePersister +import com.android.systemui.qs.external.TileLifecycleManager +import com.android.systemui.qs.external.TileServiceKey +import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository +import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository +import com.android.systemui.qs.pipeline.domain.model.TileModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.toProto +import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.repository.FakeUserRepository +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 com.google.protobuf.nano.MessageNano +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) +class CurrentTilesInteractorImplTest : SysuiTestCase() { + + private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository() + private val userRepository = FakeUserRepository() + private val tileFactory = FakeQSFactory(::tileCreator) + private val customTileAddedRepository: CustomTileAddedRepository = + FakeCustomTileAddedRepository() + private val featureFlags = FakeFeatureFlags() + private val tileLifecycleManagerFactory = TLMFactory() + + @Mock private lateinit var customTileStatePersister: CustomTileStatePersister + + @Mock private lateinit var userTracker: UserTracker + + @Mock private lateinit var logger: QSPipelineLogger + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val unavailableTiles = mutableSetOf("e") + + private lateinit var underTest: CurrentTilesInteractorImpl + + @OptIn(ExperimentalCoroutinesApi::class) + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true) + + userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) + setUserTracker(0) + + underTest = + CurrentTilesInteractorImpl( + tileSpecRepository = tileSpecRepository, + userRepository = userRepository, + customTileStatePersister = customTileStatePersister, + tileFactory = tileFactory, + customTileAddedRepository = customTileAddedRepository, + tileLifecycleManagerFactory = tileLifecycleManagerFactory, + userTracker = userTracker, + mainDispatcher = testDispatcher, + backgroundDispatcher = testDispatcher, + scope = testScope.backgroundScope, + logger = logger, + featureFlags = featureFlags, + ) + } + + @Test + fun initialState() = + testScope.runTest(USER_INFO_0) { + assertThat(underTest.currentTiles.value).isEmpty() + assertThat(underTest.currentQSTiles).isEmpty() + assertThat(underTest.currentTilesSpecs).isEmpty() + assertThat(underTest.userId.value).isEqualTo(0) + assertThat(underTest.userContext.value.userId).isEqualTo(0) + } + + @Test + fun correctTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = + listOf( + TileSpec.create("a"), + TileSpec.create("e"), + CUSTOM_TILE_SPEC, + TileSpec.create("d"), + TileSpec.create("non_existent") + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + // check each tile + + // Tile a + val tile0 = tiles!![0] + assertThat(tile0.spec).isEqualTo(specs[0]) + assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec) + assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java) + assertThat(tile0.tile.isAvailable).isTrue() + + // Tile e is not available and is not in the list + + // Custom Tile + val tile1 = tiles!![1] + assertThat(tile1.spec).isEqualTo(specs[2]) + assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec) + assertThat(tile1.tile).isInstanceOf(CustomTile::class.java) + assertThat(tile1.tile.isAvailable).isTrue() + + // Tile d + val tile2 = tiles!![2] + assertThat(tile2.spec).isEqualTo(specs[3]) + assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec) + assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java) + assertThat(tile2.tile.isAvailable).isTrue() + + // Tile non-existent shouldn't be created. Therefore, only 3 tiles total + assertThat(tiles?.size).isEqualTo(3) + } + + @Test + fun logTileCreated() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("a"), + CUSTOM_TILE_SPEC, + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + specs.forEach { verify(logger).logTileCreated(it) } + } + + @Test + fun logTileNotFoundInFactory() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("non_existing"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + verify(logger, never()).logTileCreated(any()) + verify(logger).logTileNotFoundInFactory(specs[0]) + } + + @Test + fun tileNotAvailableDestroyed_logged() = + testScope.runTest(USER_INFO_0) { + val specs = + listOf( + TileSpec.create("e"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + verify(logger, never()).logTileCreated(any()) + verify(logger) + .logTileDestroyed( + specs[0], + QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE + ) + } + + @Test + fun someTilesNotValid_repositorySetToDefinitiveList() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = + listOf( + TileSpec.create("a"), + TileSpec.create("e"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles).isEqualTo(listOf(TileSpec.create("a"))) + } + + @Test + fun deduplicatedTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("a")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(specs[0]) + } + + @Test + fun tilesChange_platformTileNotRecreated() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = + listOf( + TileSpec.create("a"), + ) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileA = tiles!![0].tile + + tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b")) + + assertThat(tiles?.size).isEqualTo(2) + assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA) + } + + @Test + fun tileRemovedIsDestroyed() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("c")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileC = tiles!![1].tile + + tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c"))) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a")) + + assertThat((originalTileC as FakeQSTile).destroyed).isTrue() + verify(logger) + .logTileDestroyed( + TileSpec.create("c"), + QSPipelineLogger.TileDestroyedReason.TILE_REMOVED + ) + } + + @Test + fun tileBecomesNotAvailable_destroyed() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a")) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + val originalTileA = tiles!![0].tile + + // Tile becomes unavailable + (originalTileA as FakeQSTile).available = false + unavailableTiles.add("a") + // and there is some change in the specs + tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b")) + runCurrent() + + assertThat(originalTileA.destroyed).isTrue() + verify(logger) + .logTileDestroyed( + TileSpec.create("a"), + QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE + ) + + assertThat(tiles?.size).isEqualTo(1) + assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b")) + assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA) + + assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec)) + } + + @Test + fun userChange_tilesChange() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs0 = listOf(TileSpec.create("a")) + val specs1 = listOf(TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs0) + tileSpecRepository.setTiles(USER_INFO_1.id, specs1) + + switchUser(USER_INFO_1) + + assertThat(tiles!![0].spec).isEqualTo(specs1[0]) + assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec) + } + + @Test + fun tileNotPresentInSecondaryUser_destroyedInUserChange() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs0 = listOf(TileSpec.create("a")) + val specs1 = listOf(TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs0) + tileSpecRepository.setTiles(USER_INFO_1.id, specs1) + + val originalTileA = tiles!![0].tile + + switchUser(USER_INFO_1) + runCurrent() + + assertThat((originalTileA as FakeQSTile).destroyed).isTrue() + verify(logger) + .logTileDestroyed( + specs0[0], + QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER + ) + } + + @Test + fun userChange_customTileDestroyed_lifecycleNotTerminated() { + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(CUSTOM_TILE_SPEC) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + tileSpecRepository.setTiles(USER_INFO_1.id, specs) + + val originalCustomTile = tiles!![0].tile + + switchUser(USER_INFO_1) + runCurrent() + + verify(originalCustomTile).destroy() + assertThat(tileLifecycleManagerFactory.created).isEmpty() + } + } + + @Test + fun userChange_sameTileUserChanged() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + + val specs = listOf(TileSpec.create("a")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + tileSpecRepository.setTiles(USER_INFO_1.id, specs) + + val originalTileA = tiles!![0].tile as FakeQSTile + assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id) + + switchUser(USER_INFO_1) + runCurrent() + + assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA) + assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id) + verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id) + } + + @Test + fun addTile() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val spec = TileSpec.create("a") + val currentSpecs = + listOf( + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + + underTest.addTile(spec, position = 1) + + val expectedSpecs = + listOf( + TileSpec.create("b"), + spec, + TileSpec.create("c"), + ) + assertThat(tiles).isEqualTo(expectedSpecs) + } + + @Test + fun addTile_currentUser() = + testScope.runTest(USER_INFO_1) { + val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) + val spec = TileSpec.create("a") + val currentSpecs = + listOf( + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) + + switchUser(USER_INFO_1) + underTest.addTile(spec, position = 1) + + assertThat(tiles0).isEqualTo(currentSpecs) + + val expectedSpecs = + listOf( + TileSpec.create("b"), + spec, + TileSpec.create("c"), + ) + assertThat(tiles1).isEqualTo(expectedSpecs) + } + + @Test + fun removeTile_platform() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a"), TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + + underTest.removeTiles(specs.subList(0, 1)) + + assertThat(tiles).isEqualTo(specs.subList(1, 2)) + } + + @Test + fun removeTile_customTile_lifecycleEnded() { + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + runCurrent() + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isTrue() + + underTest.removeTiles(listOf(CUSTOM_TILE_SPEC)) + + assertThat(tiles).isEqualTo(specs.subList(0, 1)) + + val tileLifecycleManager = + tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT] + assertThat(tileLifecycleManager).isNotNull() + + with(inOrder(tileLifecycleManager!!)) { + verify(tileLifecycleManager).onStopListening() + verify(tileLifecycleManager).onTileRemoved() + verify(tileLifecycleManager).flushMessagesAndUnbind() + } + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isFalse() + verify(customTileStatePersister) + .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)) + } + } + + @Test + fun removeTiles_currentUser() = + testScope.runTest { + val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) + val currentSpecs = + listOf( + TileSpec.create("a"), + TileSpec.create("b"), + TileSpec.create("c"), + ) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs) + + switchUser(USER_INFO_1) + runCurrent() + + underTest.removeTiles(currentSpecs.subList(0, 2)) + + assertThat(tiles0).isEqualTo(currentSpecs) + assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3)) + } + + @Test + fun setTiles() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) + + val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b")) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + runCurrent() + + val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a")) + underTest.setTiles(newSpecs) + runCurrent() + + assertThat(tiles).isEqualTo(newSpecs) + } + + @Test + fun setTiles_customTiles_lifecycleEndedIfGone() = + testScope.runTest(USER_INFO_0) { + val otherCustomTileSpec = TileSpec.create("custom(b/c)") + + val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec) + tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs) + runCurrent() + + val newSpecs = + listOf( + otherCustomTileSpec, + TileSpec.create("a"), + ) + + underTest.setTiles(newSpecs) + runCurrent() + + val tileLifecycleManager = + tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!! + + with(inOrder(tileLifecycleManager)) { + verify(tileLifecycleManager).onStopListening() + verify(tileLifecycleManager).onTileRemoved() + verify(tileLifecycleManager).flushMessagesAndUnbind() + } + assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) + .isFalse() + verify(customTileStatePersister) + .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id)) + } + + @Test + fun protoDump() = + testScope.runTest(USER_INFO_0) { + val tiles by collectLastValue(underTest.currentTiles) + val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) + + tileSpecRepository.setTiles(USER_INFO_0.id, specs) + + val stateA = tiles!![0].tile.state + stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA") + val stateCustom = QSTile.BooleanState() + stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB") + stateCustom.spec = CUSTOM_TILE_SPEC.spec + whenever(tiles!![1].tile.state).thenReturn(stateCustom) + + val proto = SystemUIProtoDump() + underTest.dumpProto(proto, emptyArray()) + + assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue() + assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto())) + .isTrue() + } + + private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { + this.state = state + this.label = label + this.secondaryLabel = secondaryLabel + if (this is BooleanState) { + value = state == Tile.STATE_ACTIVE + } + } + + private fun tileCreator(spec: String): QSTile? { + val currentUser = userTracker.userId + return when (spec) { + CUSTOM_TILE_SPEC.spec -> + mock<CustomTile> { + var tileSpecReference: String? = null + whenever(user).thenReturn(currentUser) + whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName) + whenever(isAvailable).thenReturn(true) + whenever(setTileSpec(anyString())).thenAnswer { + tileSpecReference = it.arguments[0] as? String + Unit + } + whenever(tileSpec).thenAnswer { tileSpecReference } + // Also, add it to the set of added tiles (as this happens as part of the tile + // creation). + customTileAddedRepository.setTileAdded( + CUSTOM_TILE_SPEC.componentName, + currentUser, + true + ) + } + in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles) + else -> null + } + } + + private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) { + return runTest { + switchUser(user) + body() + } + } + + private suspend fun switchUser(user: UserInfo) { + setUserTracker(user.id) + userRepository.setSelectedUserInfo(user) + } + + private fun setUserTracker(user: Int) { + val mockContext = mockUserContext(user) + whenever(userTracker.userContext).thenReturn(mockContext) + whenever(userTracker.userId).thenReturn(user) + } + + private class TLMFactory : TileLifecycleManager.Factory { + + val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>() + + override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager { + val componentName = intent.component!! + val user = userHandle.identifier + val manager: TileLifecycleManager = mock() + created[user to componentName] = manager + return manager + } + } + + private fun mockUserContext(user: Int): Context { + return mock { + whenever(this.userId).thenReturn(user) + whenever(this.user).thenReturn(UserHandle.of(user)) + } + } + + companion object { + private val USER_INFO_0 = UserInfo().apply { id = 0 } + private val USER_INFO_1 = UserInfo().apply { id = 1 } + + private val VALID_TILES = setOf("a", "b", "c", "d", "e") + private val TEST_COMPONENT = ComponentName("pkg", "cls") + private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt new file mode 100644 index 000000000000..e50969641a71 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.domain.interactor + +import android.content.Context +import android.view.View +import com.android.internal.logging.InstanceId +import com.android.systemui.plugins.qs.QSIconView +import com.android.systemui.plugins.qs.QSTile + +class FakeQSTile( + var user: Int, + var available: Boolean = true, +) : QSTile { + private var tileSpec: String? = null + var destroyed = false + private val state = QSTile.State() + + override fun getTileSpec(): String? { + return tileSpec + } + + override fun isAvailable(): Boolean { + return available + } + + override fun setTileSpec(tileSpec: String?) { + this.tileSpec = tileSpec + state.spec = tileSpec + } + + override fun refreshState() {} + + override fun addCallback(callback: QSTile.Callback?) {} + + override fun removeCallback(callback: QSTile.Callback?) {} + + override fun removeCallbacks() {} + + override fun createTileView(context: Context?): QSIconView? { + return null + } + + override fun click(view: View?) {} + + override fun secondaryClick(view: View?) {} + + override fun longClick(view: View?) {} + + override fun userSwitch(currentUser: Int) { + user = currentUser + } + + override fun getMetricsCategory(): Int { + return 0 + } + + override fun setListening(client: Any?, listening: Boolean) {} + + override fun setDetailListening(show: Boolean) {} + + override fun destroy() { + destroyed = true + } + + override fun getTileLabel(): CharSequence { + return "" + } + + override fun getState(): QSTile.State { + return state + } + + override fun getInstanceId(): InstanceId { + return InstanceId.fakeInstanceId(0) + } + + override fun isListening(): Boolean { + return false + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java index b55fe3677256..67b1099c1e0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java @@ -20,8 +20,10 @@ import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; @@ -29,6 +31,8 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; import androidx.test.runner.AndroidJUnit4; @@ -42,6 +46,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Optional; @@ -58,6 +63,9 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Mock private Optional<Bubbles> mOptionalBubbles; @Mock private Bubbles mBubbles; @Mock private DevicePolicyManager mDevicePolicyManager; + @Mock private UserManager mUserManager; + + private AppClipsService mAppClipsService; @Before public void setUp() { @@ -119,26 +127,53 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Test public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException { + mockToSatisfyAllPrerequisites(); + + assertThat(getInterfaceWithRealContext() + .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); + } + + @Test + public void isManagedProfile_shouldUseProxyConnection() throws RemoteException { + when(mUserManager.isManagedProfile()).thenReturn(true); + when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); + IAppClipsService service = getInterfaceWithRealContext(); + mAppClipsService.mProxyConnectorToMainProfile = + Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile); + + service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID); + + verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any()); + } + + @Test + public void isManagedProfile_noMainUser_shouldReturnFalse() { + when(mUserManager.isManagedProfile()).thenReturn(true); + when(mUserManager.getMainUser()).thenReturn(null); + + getInterfaceWithRealContext(); + + assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull(); + } + + private void mockToSatisfyAllPrerequisites() { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); when(mOptionalBubbles.get()).thenReturn(mBubbles); when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true); when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false); - - assertThat(getInterfaceWithRealContext() - .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); } private IAppClipsService getInterfaceWithRealContext() { - AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags, - mOptionalBubbles, mDevicePolicyManager); - return getInterfaceFromService(appClipsService); + mAppClipsService = new AppClipsService(getContext(), mFeatureFlags, + mOptionalBubbles, mDevicePolicyManager, mUserManager); + return getInterfaceFromService(mAppClipsService); } private IAppClipsService getInterfaceWithMockContext() { - AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags, - mOptionalBubbles, mDevicePolicyManager); - return getInterfaceFromService(appClipsService); + mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags, + mOptionalBubbles, mDevicePolicyManager, mUserManager); + return getInterfaceFromService(mAppClipsService); } private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java index ad06dcc6f327..31a33d4ff908 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java @@ -49,6 +49,8 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.UserHandle; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import androidx.test.rule.ActivityTestRule; @@ -98,6 +100,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private UiEventLogger mUiEventLogger; + @Mock + private UserManager mUserManager; + @Main private Handler mMainHandler; @@ -109,7 +114,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { protected AppClipsTrampolineActivityTestable create(Intent unUsed) { return new AppClipsTrampolineActivityTestable(mDevicePolicyManager, mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager, - mUserTracker, mUiEventLogger, mMainHandler); + mUserTracker, mUiEventLogger, mUserManager, mMainHandler); } }; @@ -264,6 +269,40 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE); } + @Test + public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity() + throws NameNotFoundException { + when(mUserManager.isManagedProfile()).thenReturn(true); + when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); + mockToSatisfyAllPrerequisites(); + + AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent); + waitForIdleSync(); + + Intent actualIntent = activity.mStartedIntent; + assertThat(actualIntent.getComponent()).isEqualTo( + new ComponentName(mContext, AppClipsTrampolineActivity.class)); + assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM); + } + + @Test + public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed() + throws NameNotFoundException { + when(mUserManager.isManagedProfile()).thenReturn(true); + when(mUserManager.getMainUser()).thenReturn(null); + + mockToSatisfyAllPrerequisites(); + + mActivityRule.launchActivity(mActivityIntent); + waitForIdleSync(); + + ActivityResult actualResult = mActivityRule.getActivityResult(); + assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK); + assertThat(getStatusCodeExtra(actualResult.getResultData())) + .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED); + } + private void mockToSatisfyAllPrerequisites() throws NameNotFoundException { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); @@ -282,6 +321,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public static final class AppClipsTrampolineActivityTestable extends AppClipsTrampolineActivity { + Intent mStartedIntent; + UserHandle mStartingUser; + public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, @@ -289,9 +331,10 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, + UserManager userManager, @Main Handler mainHandler) { super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager, - userTracker, uiEventLogger, mainHandler); + userTracker, uiEventLogger, userManager, mainHandler); } @Override @@ -303,6 +346,12 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public void startActivity(Intent unUsed) { // Ignore this intent to avoid App Clips screenshot editing activity from starting. } + + @Override + public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) { + mStartedIntent = startedIntent; + mStartingUser = startingUser; + } } private static int getStatusCodeExtra(Intent intent) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 7b37ea0c9a1a..5ca37716cbff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -66,6 +67,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; +import com.android.keyguard.KeyguardSliceViewController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; @@ -74,6 +76,7 @@ import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; import com.android.keyguard.dagger.KeyguardUserSwitcherComponent; +import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; @@ -106,6 +109,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.QSFragment; @@ -232,7 +236,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; - @Mock protected KeyguardStatusViewController mKeyguardStatusViewController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @Mock protected NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @@ -292,9 +295,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor; @Mock protected MotionEvent mDownMotionEvent; @Mock protected CoroutineDispatcher mMainDispatcher; + @Mock protected KeyguardSliceViewController mKeyguardSliceViewController; + @Mock protected KeyguardLogger mKeyguardLogger; + @Mock protected KeyguardStatusView mKeyguardStatusView; @Captor protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> mEmptySpaceClickListenerCaptor; + @Mock protected ActivityStarter mActivityStarter; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected KeyguardInteractor mKeyguardInteractor; @@ -307,6 +314,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; protected Handler mMainHandler; protected View.OnLayoutChangeListener mLayoutChangeListener; + protected KeyguardStatusViewController mKeyguardStatusViewController; protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake(); protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); @@ -333,6 +341,18 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); keyguardStatusView.setId(R.id.keyguard_status_view); + mKeyguardStatusViewController = spy(new KeyguardStatusViewController( + mKeyguardStatusView, + mKeyguardSliceViewController, + mKeyguardClockSwitchController, + mKeyguardStateController, + mUpdateMonitor, + mConfigurationController, + mDozeParameters, + mScreenOffAnimationController, + mKeyguardLogger, + mFeatureFlags, + mInteractionJankMonitor)); when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false); when(mHeadsUpCallback.getContext()).thenReturn(mContext); @@ -364,12 +384,15 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea); when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator); when(mView.animate()).thenReturn(mViewPropertyAnimator); + when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator); when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator); + when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator); when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); when(mView.findViewById(R.id.keyguard_status_view)) .thenReturn(mock(KeyguardStatusView.class)); @@ -575,7 +598,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { () -> mMultiShadeInteractor, mDumpManager, mKeyuardLongPressViewModel, - mKeyguardInteractor); + mKeyguardInteractor, + mActivityStarter); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, @@ -647,9 +671,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @After public void tearDown() { - mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); - mNotificationPanelViewController.cancelHeightAnimator(); - mMainHandler.removeCallbacksAndMessages(null); + if (mNotificationPanelViewController != null) { + mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); + mNotificationPanelViewController.cancelHeightAnimator(); + } + if (mMainHandler != null) { + mMainHandler.removeCallbacksAndMessages(null); + } } protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index d5308298202d..b043e97f1054 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -42,11 +42,11 @@ import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController -import com.android.systemui.qs.carrier.QSCarrierGroup -import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT +import com.android.systemui.shade.carrier.ShadeCarrierGroup +import com.android.systemui.shade.carrier.ShadeCarrierGroupController import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer @@ -88,11 +88,12 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var statusBarIconController: StatusBarIconController @Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory @Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager - @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController - @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder + @Mock private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController + @Mock + private lateinit var mShadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder @Mock private lateinit var clock: Clock @Mock private lateinit var date: VariableDateView - @Mock private lateinit var carrierGroup: QSCarrierGroup + @Mock private lateinit var carrierGroup: ShadeCarrierGroup @Mock private lateinit var batteryMeterView: BatteryMeterView @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController @@ -131,7 +132,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date) whenever(date.context).thenReturn(mockedContext) - whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup) + whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup) whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon)) .thenReturn(batteryMeterView) @@ -142,9 +143,10 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever(view.context).thenReturn(viewContext) whenever(view.resources).thenReturn(context.resources) whenever(statusIcons.context).thenReturn(context) - whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any())) - .thenReturn(qsCarrierGroupControllerBuilder) - whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) + whenever(mShadeCarrierGroupControllerBuilder.setShadeCarrierGroup(any())) + .thenReturn(mShadeCarrierGroupControllerBuilder) + whenever(mShadeCarrierGroupControllerBuilder.build()) + .thenReturn(mShadeCarrierGroupController) whenever(view.setVisibility(anyInt())).then { viewVisibility = it.arguments[0] as Int null @@ -175,7 +177,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { variableDateViewControllerFactory, batteryMeterViewController, dumpManager, - qsCarrierGroupControllerBuilder, + mShadeCarrierGroupControllerBuilder, combinedShadeHeadersConstraintManager, demoModeController, qsBatteryModeController, @@ -189,7 +191,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Test fun updateListeners_registersWhenVisible() { makeShadeVisible() - verify(qsCarrierGroupController).setListening(true) + verify(mShadeCarrierGroupController).setListening(true) verify(statusBarIconController).addIconGroup(any()) } @@ -213,7 +215,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Test fun singleCarrier_enablesCarrierIconsInStatusIcons() { - whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true) + whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(true) makeShadeVisible() @@ -222,7 +224,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Test fun dualCarrier_disablesCarrierIconsInStatusIcons() { - whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false) + whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) makeShadeVisible() @@ -349,9 +351,9 @@ class ShadeHeaderControllerTest : SysuiTestCase() { verify(batteryMeterViewController).init() verify(batteryMeterViewController).ignoreTunerUpdates() - val inOrder = Mockito.inOrder(qsCarrierGroupControllerBuilder) - inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup) - inOrder.verify(qsCarrierGroupControllerBuilder).build() + val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder) + inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup) + inOrder.verify(mShadeCarrierGroupControllerBuilder).build() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt index 75be74b13c87..7a9ef62278a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier +package com.android.systemui.shade.carrier import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest @@ -45,4 +45,4 @@ class CellSignalStateTest : SysuiTestCase() { assertNotSame(c, other) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java index 1e7722ae8395..2ef3d60c7754 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier; +package com.android.systemui.shade.carrier; import static com.google.common.truth.Truth.assertThat; @@ -63,13 +63,13 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class QSCarrierGroupControllerTest extends LeakCheckedTest { +public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { - private QSCarrierGroupController mQSCarrierGroupController; + private ShadeCarrierGroupController mShadeCarrierGroupController; private SignalCallback mSignalCallback; private CarrierTextManager.CarrierTextCallback mCallback; @Mock - private QSCarrierGroup mQSCarrierGroup; + private ShadeCarrierGroup mShadeCarrierGroup; @Mock private ActivityStarter mActivityStarter; @Mock @@ -81,14 +81,14 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { @Mock private CarrierConfigTracker mCarrierConfigTracker; @Mock - private QSCarrier mQSCarrier1; + private ShadeCarrier mShadeCarrier1; @Mock - private QSCarrier mQSCarrier2; + private ShadeCarrier mShadeCarrier2; @Mock - private QSCarrier mQSCarrier3; + private ShadeCarrier mShadeCarrier3; private TestableLooper mTestableLooper; @Mock - private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener; + private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener; private FakeSlotIndexResolver mSlotIndexResolver; private ClickListenerTextView mNoCarrierTextView; @@ -116,28 +116,28 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { .setListening(any(CarrierTextManager.CarrierTextCallback.class)); mNoCarrierTextView = new ClickListenerTextView(mContext); - when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView); - when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1); - when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2); - when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3); - when(mQSCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext)); - when(mQSCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext)); + when(mShadeCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView); + when(mShadeCarrierGroup.getCarrier1View()).thenReturn(mShadeCarrier1); + when(mShadeCarrierGroup.getCarrier2View()).thenReturn(mShadeCarrier2); + when(mShadeCarrierGroup.getCarrier3View()).thenReturn(mShadeCarrier3); + when(mShadeCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext)); + when(mShadeCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext)); mSlotIndexResolver = new FakeSlotIndexResolver(); - mQSCarrierGroupController = new QSCarrierGroupController.Builder( + mShadeCarrierGroupController = new ShadeCarrierGroupController.Builder( mActivityStarter, handler, TestableLooper.get(this).getLooper(), mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker, mSlotIndexResolver) - .setQSCarrierGroup(mQSCarrierGroup) + .setShadeCarrierGroup(mShadeCarrierGroup) .build(); - mQSCarrierGroupController.setListening(true); + mShadeCarrierGroupController.setListening(true); } @Test public void testInitiallyMultiCarrier() { - assertFalse(mQSCarrierGroupController.isSingleCarrier()); + assertFalse(mShadeCarrierGroupController.isSingleCarrier()); } @Test // throws no Exception @@ -257,12 +257,12 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { true /* airplaneMode */); mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); - assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility()); + assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility()); } @Test public void testListenerNotCalledOnRegistreation() { - mQSCarrierGroupController + mShadeCarrierGroupController .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener); verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean()); @@ -282,9 +282,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); - verify(mQSCarrier1).updateState(any(), eq(true)); - verify(mQSCarrier2).updateState(any(), eq(true)); - verify(mQSCarrier3).updateState(any(), eq(true)); + verify(mShadeCarrier1).updateState(any(), eq(true)); + verify(mShadeCarrier2).updateState(any(), eq(true)); + verify(mShadeCarrier3).updateState(any(), eq(true)); } @Test @@ -301,9 +301,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(info); mTestableLooper.processAllMessages(); - verify(mQSCarrier1).updateState(any(), eq(false)); - verify(mQSCarrier2).updateState(any(), eq(false)); - verify(mQSCarrier3).updateState(any(), eq(false)); + verify(mShadeCarrier1).updateState(any(), eq(false)); + verify(mShadeCarrier2).updateState(any(), eq(false)); + verify(mShadeCarrier3).updateState(any(), eq(false)); } @Test @@ -327,7 +327,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(singleCarrierInfo); mTestableLooper.processAllMessages(); - mQSCarrierGroupController + mShadeCarrierGroupController .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener); reset(mOnSingleCarrierChangedListener); @@ -353,7 +353,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(singleCarrierInfo); mTestableLooper.processAllMessages(); - mQSCarrierGroupController + mShadeCarrierGroupController .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener); mCallback.updateCarrierInfo(singleCarrierInfo); @@ -375,7 +375,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { mCallback.updateCarrierInfo(multiCarrierInfo); mTestableLooper.processAllMessages(); - mQSCarrierGroupController + mShadeCarrierGroupController .setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener); mCallback.updateCarrierInfo(multiCarrierInfo); @@ -389,12 +389,12 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(View.OnClickListener.class); - verify(mQSCarrier1).setOnClickListener(captor.capture()); - verify(mQSCarrier2).setOnClickListener(captor.getValue()); - verify(mQSCarrier3).setOnClickListener(captor.getValue()); + verify(mShadeCarrier1).setOnClickListener(captor.capture()); + verify(mShadeCarrier2).setOnClickListener(captor.getValue()); + verify(mShadeCarrier3).setOnClickListener(captor.getValue()); assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue()); - verify(mQSCarrierGroup, never()).setOnClickListener(any()); + verify(mShadeCarrierGroup, never()).setOnClickListener(any()); } @Test @@ -402,10 +402,10 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(View.OnClickListener.class); - verify(mQSCarrier1).setOnClickListener(captor.capture()); - when(mQSCarrier1.isVisibleToUser()).thenReturn(false); + verify(mShadeCarrier1).setOnClickListener(captor.capture()); + when(mShadeCarrier1.isVisibleToUser()).thenReturn(false); - captor.getValue().onClick(mQSCarrier1); + captor.getValue().onClick(mShadeCarrier1); verifyZeroInteractions(mActivityStarter); } @@ -415,17 +415,17 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest { ArgumentCaptor.forClass(View.OnClickListener.class); ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture()); - when(mQSCarrier1.isVisibleToUser()).thenReturn(true); + verify(mShadeCarrier1).setOnClickListener(listenerCaptor.capture()); + when(mShadeCarrier1.isVisibleToUser()).thenReturn(true); - listenerCaptor.getValue().onClick(mQSCarrier1); + listenerCaptor.getValue().onClick(mShadeCarrier1); verify(mActivityStarter) .postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt()); assertThat(intentCaptor.getValue().getAction()) .isEqualTo(Settings.ACTION_WIRELESS_SETTINGS); } - private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver { + private class FakeSlotIndexResolver implements ShadeCarrierGroupController.SlotIndexResolver { public boolean overrideInvalid; @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java index 9115ab3bacca..44613103a5b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java @@ -1,11 +1,11 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.qs.carrier; +package com.android.systemui.shade.carrier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -39,9 +39,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class QSCarrierTest extends SysuiTestCase { +public class ShadeCarrierTest extends SysuiTestCase { - private QSCarrier mQSCarrier; + private ShadeCarrier mShadeCarrier; private TestableLooper mTestableLooper; private int mSignalIconId; @@ -51,7 +51,7 @@ public class QSCarrierTest extends SysuiTestCase { LayoutInflater inflater = LayoutInflater.from(mContext); mContext.ensureTestableResources(); mTestableLooper.runWithLooper(() -> - mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null)); + mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null)); // In this case, the id is an actual drawable id mSignalIconId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0]; @@ -61,76 +61,76 @@ public class QSCarrierTest extends SysuiTestCase { public void testUpdateState_first() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - assertTrue(mQSCarrier.updateState(c, false)); + assertTrue(mShadeCarrier.updateState(c, false)); } @Test public void testUpdateState_same() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - assertTrue(mQSCarrier.updateState(c, false)); - assertFalse(mQSCarrier.updateState(c, false)); + assertTrue(mShadeCarrier.updateState(c, false)); + assertFalse(mShadeCarrier.updateState(c, false)); } @Test public void testUpdateState_changed() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - assertTrue(mQSCarrier.updateState(c, false)); + assertTrue(mShadeCarrier.updateState(c, false)); CellSignalState other = c.changeVisibility(false); - assertTrue(mQSCarrier.updateState(other, false)); + assertTrue(mShadeCarrier.updateState(other, false)); } @Test public void testUpdateState_singleCarrier_first() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - assertTrue(mQSCarrier.updateState(c, true)); + assertTrue(mShadeCarrier.updateState(c, true)); } @Test public void testUpdateState_singleCarrier_noShowIcon() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - mQSCarrier.updateState(c, true); + mShadeCarrier.updateState(c, true); - assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility()); + assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility()); } @Test public void testUpdateState_multiCarrier_showIcon() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - mQSCarrier.updateState(c, false); + mShadeCarrier.updateState(c, false); - assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility()); + assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility()); } @Test public void testUpdateState_changeSingleMultiSingle() { CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false); - mQSCarrier.updateState(c, true); - assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility()); + mShadeCarrier.updateState(c, true); + assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility()); - mQSCarrier.updateState(c, false); - assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility()); + mShadeCarrier.updateState(c, false); + assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility()); - mQSCarrier.updateState(c, true); - assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility()); + mShadeCarrier.updateState(c, true); + assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility()); } @Test public void testCarrierNameMaxWidth_smallScreen_fromResource() { int maxEms = 10; - mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms); + mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms); mContext.getOrCreateTestableResources() .addOverride(R.bool.config_use_large_screen_shade_header, false); - TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text); + TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text); - mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); + mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); assertEquals(maxEms, carrierText.getMaxEms()); } @@ -138,12 +138,12 @@ public class QSCarrierTest extends SysuiTestCase { @Test public void testCarrierNameMaxWidth_largeScreen_maxInt() { int maxEms = 10; - mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms); + mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms); mContext.getOrCreateTestableResources() .addOverride(R.bool.config_use_large_screen_shade_header, true); - TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text); + TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text); - mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); + mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 569f90b64609..4438b98f6fad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -614,9 +614,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceFailure() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -641,9 +640,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceUnavailable() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -664,6 +662,35 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mContext.getString(R.string.keyguard_suggest_fingerprint)); } + + @Test + public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() { + createController(); + + // GIVEN unlocking with fingerprint is possible but not allowed + setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) + .thenReturn(false); + + String message = "A message"; + mController.setVisible(true); + + // WHEN there's a face unavailable message + mController.getKeyguardCallback().onBiometricHelp( + BIOMETRIC_HELP_FACE_NOT_AVAILABLE, + message, + BiometricSourceType.FACE); + + // THEN show sequential messages such as: 'face unlock unavailable' and + // 'try fingerprint instead' + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + message); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + @Test public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() { createController(); @@ -818,8 +845,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() { createController(); - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -832,9 +858,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that are allowed to show final String helpString = "helpString"; @@ -859,9 +884,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that aren't supposed to show final String helpString = "helpString"; @@ -886,9 +910,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendAllFaceHelpMessages_fingerprintNotEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint NOT possible + fingerprintUnlockIsNotPossible(); // WHEN help messages received final Set<CharSequence> helpStrings = new HashSet<>(); @@ -917,9 +940,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint not possible + fingerprintUnlockIsNotPossible(); // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; @@ -948,9 +970,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; @@ -1500,7 +1521,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, @@ -1559,7 +1580,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); clearInvocations(mRotateTextViewController); @@ -1668,7 +1689,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() { createController(); screenIsTurningOn(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("lockout error"); verifyNoMoreInteractions(mRotateTextViewController); @@ -1746,8 +1767,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { setupFingerprintUnlockPossible(false); } - private void fingerprintUnlockIsPossible() { + private void fingerprintUnlockIsPossibleAndAllowed() { setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true); } private void setupFingerprintUnlockPossible(boolean possible) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 7b59cc284181..08a9f3139d71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler private val fakeFeatureFlags = FakeFeatureFlags() - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index be3b7234a1a2..aff705f1f3bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() private val dumpManager: DumpManager = mock() private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 30708a7cb2fe..ac66ad9e9c8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -97,6 +97,59 @@ public class HighPriorityProviderTest extends SysuiTestCase { } @Test + public void highImportanceConversation() { + // GIVEN notification is high importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_HIGH) + .build(); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(entry)) + .thenReturn(TYPE_PERSON); + + // THEN it is high priority conversation + assertTrue(mHighPriorityProvider.isHighPriorityConversation(entry)); + } + + @Test + public void lowImportanceConversation() { + // GIVEN notification is high importance and is a people notification + final Notification notification = new Notification.Builder(mContext, "test") + .build(); + final NotificationEntry entry = new NotificationEntryBuilder() + .setNotification(notification) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(entry)) + .thenReturn(TYPE_PERSON); + + // THEN it is low priority conversation + assertFalse(mHighPriorityProvider.isHighPriorityConversation(entry)); + } + + @Test + public void highImportanceConversationWhenAnyOfChildIsHighPriority() { + // GIVEN notification is high importance and is a people notification + final NotificationEntry summary = createNotifEntry(false); + final NotificationEntry lowPriorityChild = createNotifEntry(false); + final NotificationEntry highPriorityChild = createNotifEntry(true); + when(mPeopleNotificationIdentifier + .getPeopleNotificationType(summary)) + .thenReturn(TYPE_PERSON); + final GroupEntry groupEntry = new GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(List.of(lowPriorityChild, highPriorityChild)) + .build(); + + // THEN the groupEntry is high priority conversation since it has a high priority child + assertTrue(mHighPriorityProvider.isHighPriorityConversation(groupEntry)); + } + + @Test public void messagingStyle() { // GIVEN notification is low importance but has messaging style final Notification notification = new Notification.Builder(mContext, "test") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 742fcf5e03c3..55ea31571dfe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -17,6 +17,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.NotificationChannel +import android.app.NotificationManager.IMPORTANCE_DEFAULT +import android.app.NotificationManager.IMPORTANCE_HIGH +import android.app.NotificationManager.IMPORTANCE_LOW import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -31,10 +34,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner +import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider +import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.withArgCaptor @@ -55,7 +61,8 @@ import org.mockito.Mockito.`when` as whenever class ConversationCoordinatorTest : SysuiTestCase() { // captured listeners and pluggables: private lateinit var promoter: NotifPromoter - private lateinit var peopleSectioner: NotifSectioner + private lateinit var peopleAlertingSectioner: NotifSectioner + private lateinit var peopleSilentSectioner: NotifSectioner private lateinit var peopleComparator: NotifComparator private lateinit var beforeRenderListListener: OnBeforeRenderListListener @@ -76,6 +83,7 @@ class ConversationCoordinatorTest : SysuiTestCase() { coordinator = ConversationCoordinator( peopleNotificationIdentifier, conversationIconManager, + HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), headerController ) whenever(channel.isImportantConversation).thenReturn(true) @@ -90,12 +98,13 @@ class ConversationCoordinatorTest : SysuiTestCase() { verify(pipeline).addOnBeforeRenderListListener(capture()) } - peopleSectioner = coordinator.sectioner - peopleComparator = peopleSectioner.comparator!! + peopleAlertingSectioner = coordinator.peopleAlertingSectioner + peopleSilentSectioner = coordinator.peopleSilentSectioner + peopleComparator = peopleAlertingSectioner.comparator!! entry = NotificationEntryBuilder().setChannel(channel).build() - val section = NotifSection(peopleSectioner, 0) + val section = NotifSection(peopleAlertingSectioner, 0) entryA = NotificationEntryBuilder().setChannel(channel) .setSection(section).setTag("A").build() entryB = NotificationEntryBuilder().setChannel(channel) @@ -129,13 +138,67 @@ class ConversationCoordinatorTest : SysuiTestCase() { } @Test - fun testInPeopleSection() { + fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { + // GIVEN + val alertingEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_DEFAULT).build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) + .thenReturn(TYPE_PERSON) + + // put alerting people notifications in this section + assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() + } + + @Test + fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { + // GIVEN + val silentEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_LOW).build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) + .thenReturn(TYPE_PERSON) + + // THEN put silent people notifications in this section + assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() + // People Alerting sectioning happens before the silent one. + // It claims high important conversations and rest of conversations will be considered as silent. + assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse() + } + + @Test + fun testNotInPeopleSection() { + // GIVEN + val entry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_LOW).build() + val importantEntry = NotificationEntryBuilder().setChannel(channel) + .setImportance(IMPORTANCE_HIGH).build() whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_PERSON) + .thenReturn(TYPE_NON_PERSON) + whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) + .thenReturn(TYPE_NON_PERSON) - // only put people notifications in this section - assertTrue(peopleSectioner.isInSection(entry)) - assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build())) + // THEN - only put people notification either silent or alerting + assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() + assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse() + } + + @Test + fun testInAlertingPeopleSectionWhenThereIsAnImportantChild(){ + // GIVEN + val altChildA = NotificationEntryBuilder().setTag("A") + .setImportance(IMPORTANCE_DEFAULT).build() + val altChildB = NotificationEntryBuilder().setTag("B") + .setImportance(IMPORTANCE_LOW).build() + val summary = NotificationEntryBuilder().setId(2) + .setImportance(IMPORTANCE_LOW).setChannel(channel).build() + val groupEntry = GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(altChildA, altChildB)) + .build() + whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) + .thenReturn(TYPE_PERSON) + // THEN + assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index d5c0c5564af6..3d1253e2b05d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -52,7 +52,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider; -import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider; import com.android.systemui.statusbar.notification.collection.render.NodeController; import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; @@ -73,7 +72,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private SectionStyleProvider mSectionStyleProvider; @Mock private NotifPipeline mNotifPipeline; @Mock private NodeController mAlertingHeaderController; @Mock private NodeController mSilentNodeController; @@ -100,7 +98,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { mRankingCoordinator = new RankingCoordinator( mStatusBarStateController, mHighPriorityProvider, - mSectionStyleProvider, mAlertingHeaderController, mSilentHeaderController, mSilentNodeController); @@ -108,7 +105,6 @@ public class RankingCoordinatorTest extends SysuiTestCase { mEntry.setRanking(getRankingForUnfilteredNotif().build()); mRankingCoordinator.attach(mNotifPipeline); - verify(mSectionStyleProvider).setMinimizedSections(any()); verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture()); mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0); mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt new file mode 100644 index 000000000000..cbb08946a1b0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -0,0 +1,78 @@ +package com.android.systemui.statusbar.notification.interruption + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl +import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() { + + @Test + fun decisionOfTrue() { + assertTrue(DecisionImpl.of(true).shouldInterrupt) + } + + @Test + fun decisionOfFalse() { + assertFalse(DecisionImpl.of(false).shouldInterrupt) + } + + @Test + fun decisionOfTrueInterned() { + assertEquals(DecisionImpl.of(true), DecisionImpl.of(true)) + } + + @Test + fun decisionOfFalseInterned() { + assertEquals(DecisionImpl.of(false), DecisionImpl.of(false)) + } + + @Test + fun fullScreenIntentDecisionShouldInterrupt() { + makeFsiDecision(FSI_DEVICE_NOT_INTERACTIVE).let { + assertTrue(it.shouldInterrupt) + assertFalse(it.wouldInterruptWithoutDnd) + } + } + + @Test + fun fullScreenIntentDecisionShouldNotInterrupt() { + makeFsiDecision(NO_FSI_NOT_IMPORTANT_ENOUGH).let { + assertFalse(it.shouldInterrupt) + assertFalse(it.wouldInterruptWithoutDnd) + } + } + + @Test + fun fullScreenIntentDecisionWouldInterruptWithoutDnd() { + makeFsiDecision(NO_FSI_SUPPRESSED_ONLY_BY_DND).let { + assertFalse(it.shouldInterrupt) + assertTrue(it.wouldInterruptWithoutDnd) + } + } + + @Test + fun fullScreenIntentDecisionWouldNotInterruptEvenWithoutDnd() { + makeFsiDecision(NO_FSI_SUPPRESSED_BY_DND).let { + assertFalse(it.shouldInterrupt) + assertFalse(it.wouldInterruptWithoutDnd) + } + } + + private fun makeFsiDecision(originalDecision: FullScreenIntentDecision) = + FullScreenIntentDecisionImpl(NotificationEntryBuilder().build(), originalDecision) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 32f0adfa1954..48710a42f616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -133,6 +133,7 @@ import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -221,6 +222,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private NotificationListContainer mNotificationListContainer; @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; + @Mock private ShadeLogger mShadeLogger; @Mock private NotificationPanelView mNotificationPanelView; @Mock private QuickSettingsController mQuickSettingsController; @Mock private IStatusBarService mBarService; @@ -469,6 +471,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mKeyguardViewMediator, new DisplayMetrics(), mMetricsLogger, + mShadeLogger, mUiBgExecutor, mNotificationMediaManager, mLockscreenUserManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index f9c72d523673..3591c1740329 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -67,6 +67,8 @@ class FakeMobileConnectionsRepository( override val mobileIsDefault = MutableStateFlow(false) + override val hasCarrierMergedConnection = MutableStateFlow(false) + override val defaultConnectionIsValidated = MutableStateFlow(false) private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>() 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 9d294cf098e2..7cc59b67414b 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 @@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.After import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue @@ -766,9 +767,66 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun mobileIsDefault_carrierMergedViaMobile_isDefault() = + runBlocking(IMMEDIATE) { + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun mobileIsDefault_wifiDefault_mobileNotDefault() = + runBlocking(IMMEDIATE) { + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + } + + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun mobileIsDefault_ethernetDefault_mobileNotDefault() = + runBlocking(IMMEDIATE) { + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + } + + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isFalse() + + job.cancel() + } + /** Regression test for b/272586234. */ @Test - fun mobileIsDefault_carrierMergedViaWifi_isDefault() = + fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() = runBlocking(IMMEDIATE) { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } @@ -779,9 +837,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() assertThat(latest).isTrue() @@ -789,7 +848,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun mobileIsDefault_carrierMergedViaMobile_isDefault() = + fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() = runBlocking(IMMEDIATE) { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } @@ -800,9 +859,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() assertThat(latest).isTrue() @@ -811,7 +871,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { /** Regression test for b/272586234. */ @Test - fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() = + fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() = runBlocking(IMMEDIATE) { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } @@ -822,9 +882,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() assertThat(latest).isTrue() @@ -832,7 +893,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() = + fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() = runBlocking(IMMEDIATE) { val carrierMergedInfo = mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } @@ -843,9 +904,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() assertThat(latest).isTrue() @@ -853,37 +915,100 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun mobileIsDefault_wifiDefault_mobileNotDefault() = + fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() = runBlocking(IMMEDIATE) { - val caps = + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + val underlyingNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingWifiCapabilities = mock<NetworkCapabilities>().also { whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } - getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + yield() - assertThat(latest).isFalse() + // THEN there's a carrier merged connection + assertThat(latest).isTrue() job.cancel() } @Test - fun mobileIsDefault_ethernetDefault_mobileNotDefault() = + fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() = runBlocking(IMMEDIATE) { - val caps = + var latest: Boolean? = null + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = mock<NetworkCapabilities>().also { - whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) } + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + yield() + + // THEN there's a carrier merged connection + assertThat(latest).isTrue() + + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() = + runBlocking(IMMEDIATE) { var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this) + // WHEN the default callback isn't carrier merged + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) } + val caps = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + yield() - assertThat(latest).isFalse() + // BUT the wifi repo has gotten updates that it *is* carrier merged + wifiRepository.setWifiNetwork(WIFI_NETWORK_CM) + yield() + + // THEN hasCarrierMergedConnection is true + assertThat(latest).isTrue() job.cancel() } 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 c5ceacac6446..dc683868f727 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 @@ -456,20 +456,63 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test - fun mobileIsDefault_usesRepoValue() = + fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + + connectionsRepository.mobileIsDefault.value = false + connectionsRepository.hasCarrierMergedConnection.value = false + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = testScope.runTest { var latest: Boolean? = null val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) connectionsRepository.mobileIsDefault.value = true + connectionsRepository.hasCarrierMergedConnection.value = false + assertThat(latest).isTrue() + job.cancel() + } + + /** Regression test for b/272586234. */ + @Test + fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + connectionsRepository.mobileIsDefault.value = false - assertThat(latest).isFalse() + connectionsRepository.hasCarrierMergedConnection.value = true + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun mobileIsDefault_updatesWhenRepoUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) connectionsRepository.mobileIsDefault.value = true assertThat(latest).isTrue() + connectionsRepository.mobileIsDefault.value = false + assertThat(latest).isFalse() + + connectionsRepository.hasCarrierMergedConnection.value = true + assertThat(latest).isTrue() + job.cancel() } 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 1b6ab4d7af96..297cb9d691ba 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 @@ -179,15 +179,71 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test - fun iconId_cutout_whenDefaultDataDisabled() = + fun icon_usesLevelFromInteractor() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + interactor.level.value = 3 + assertThat(latest!!.level).isEqualTo(3) + + interactor.level.value = 1 + assertThat(latest!!.level).isEqualTo(1) + + job.cancel() + } + + @Test + fun icon_usesNumberOfLevelsFromInteractor() = + testScope.runTest { + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + interactor.numberOfLevels.value = 5 + assertThat(latest!!.numberOfLevels).isEqualTo(5) + + interactor.numberOfLevels.value = 2 + assertThat(latest!!.numberOfLevels).isEqualTo(2) + + job.cancel() + } + + @Test + fun icon_defaultDataDisabled_showExclamationTrue() = testScope.runTest { interactor.setIsDefaultDataEnabled(false) var latest: SignalIconModel? = null val job = underTest.icon.onEach { latest = it }.launchIn(this) - val expected = defaultSignal(level = 1, connected = false) - assertThat(latest).isEqualTo(expected) + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_defaultConnectionFailed_showExclamationTrue() = + testScope.runTest { + interactor.isDefaultConnectionFailed.value = true + + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isTrue() + + job.cancel() + } + + @Test + fun icon_enabledAndNotFailed_showExclamationFalse() = + testScope.runTest { + interactor.setIsDefaultDataEnabled(true) + interactor.isDefaultConnectionFailed.value = false + + var latest: SignalIconModel? = null + val job = underTest.icon.onEach { latest = it }.launchIn(this) + + assertThat(latest!!.showExclamationMark).isFalse() job.cancel() } @@ -572,16 +628,14 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 + private const val NUM_LEVELS = 4 /** Convenience constructor for these tests */ - fun defaultSignal( - level: Int = 1, - connected: Boolean = true, - ): SignalIconModel { - return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected) + fun defaultSignal(level: Int = 1): SignalIconModel { + return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false) } fun emptySignal(): SignalIconModel = - SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true) + SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt index 87d4f5c618a7..661002d275b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt @@ -33,6 +33,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY +import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo import com.android.systemui.tuner.TunerService import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor @@ -491,6 +492,111 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { } @Test + fun defaultConnections_nullUnderlyingInfo_noError() { + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(null) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + // No assert, just verify no error + } + + @Test + fun defaultConnections_underlyingInfoHasNullCapabilities_noError() { + val underlyingNetworkWithNull = mock<Network>() + whenever(connectivityManager.getNetworkCapabilities(underlyingNetworkWithNull)) + .thenReturn(null) + + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetworkWithNull)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + // No assert, just verify no error + } + + // This test verifies our internal API for completeness, but we don't expect this case to ever + // happen in practice. + @Test + fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() = + testScope.runTest { + var latest: DefaultConnectionModel? = null + val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this) + + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + assertThat(latest!!.mobile.isDefault).isTrue() + assertThat(latest!!.carrierMerged.isDefault).isTrue() + assertThat(latest!!.wifi.isDefault).isTrue() + + job.cancel() + } + + /** Test for b/225902574. */ + @Test + fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() = + testScope.runTest { + var latest: DefaultConnectionModel? = null + val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this) + + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + assertThat(latest!!.mobile.isDefault).isTrue() + assertThat(latest!!.carrierMerged.isDefault).isTrue() + assertThat(latest!!.wifi.isDefault).isTrue() + + job.cancel() + } + + @Test fun defaultConnections_multipleTransports_multipleDefault() = testScope.runTest { var latest: DefaultConnectionModel? = null @@ -548,6 +654,279 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() { + val wifiInfo = mock<WifiInfo>() + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(wifiInfo) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() { + val wifiInfo = mock<WifiInfo>() + val vcnInfo = VcnTransportInfo(wifiInfo) + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(vcnInfo) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellularOrWifiTransport_noInfo() { + val capabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(mock<WifiInfo>()) + } + + val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingWifi_hasInfo() { + val underlyingNetwork = mock<Network>() + val underlyingWifiInfo = mock<WifiInfo>() + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we fetch the underlying wifi info + assertThat(result).isEqualTo(underlyingWifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() { + val underlyingNetwork = mock<Network>() + val underlyingWifiInfo = mock<WifiInfo>() + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network but is *not* CELLULAR + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we DON'T fetch the underlying wifi info + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() { + val wifiInfo = mock<WifiInfo>() + val underlyingNetwork = mock<Network>() + val underlyingVcnInfo = VcnTransportInfo(wifiInfo) + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying VCN network with wifi + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we fetch the wifi info + assertThat(result).isEqualTo(wifiInfo) + } + + @Test + fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() { + val underlyingNetwork = mock<Network>() + val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>()) + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingVcnInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying wifi network but it is *not* CELLULAR + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true) + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN we DON'T fetch the underlying wifi info + assertThat(result).isNull() + } + + @Test + fun getMainOrUnderlyingWifiInfo_cellular_underlyingCellularWithCarrierMerged_hasInfo() { + // Underlying carrier merged network + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network with underlying network + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + assertThat(result).isEqualTo(carrierMergedInfo) + assertThat(result!!.isCarrierMerged).isTrue() + } + + @Test + fun getMainOrUnderlyingWifiInfo_multipleUnderlying_usesFirstNonNull() { + // First underlying: Not wifi + val underlyingNotWifiNetwork = mock<Network>() + val underlyingNotWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false) + whenever(it.transportInfo).thenReturn(null) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNotWifiNetwork)) + .thenReturn(underlyingNotWifiCapabilities) + + // Second underlying: wifi + val underlyingWifiNetwork1 = mock<Network>() + val underlyingWifiInfo1 = mock<WifiInfo>() + val underlyingWifiCapabilities1 = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo1) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork1)) + .thenReturn(underlyingWifiCapabilities1) + + // Third underlying: also wifi + val underlyingWifiNetwork2 = mock<Network>() + val underlyingWifiInfo2 = mock<WifiInfo>() + val underlyingWifiCapabilities2 = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(underlyingWifiInfo2) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork2)) + .thenReturn(underlyingWifiCapabilities2) + + // WHEN the main capabilities has multiple underlying networks + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn( + listOf( + underlyingNotWifiNetwork, + underlyingWifiNetwork1, + underlyingWifiNetwork2, + ) + ) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN the first wifi one is used + assertThat(result).isEqualTo(underlyingWifiInfo1) + } + + @Test + fun getMainOrUnderlyingWifiInfo_nestedUnderlying_doesNotLookAtNested() { + // WHEN there are two layers of underlying networks... + + // Nested network + val nestedUnderlyingNetwork = mock<Network>() + val nestedWifiInfo = mock<WifiInfo>() + val nestedCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(nestedWifiInfo) + } + whenever(connectivityManager.getNetworkCapabilities(nestedUnderlyingNetwork)) + .thenReturn(nestedCapabilities) + + // Underlying network containing the nested network + val underlyingNetwork = mock<Network>() + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(nestedUnderlyingNetwork)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingCapabilities) + + // Main network containing the underlying network, which contains the nested network + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager) + + // THEN only the first layer is checked, and the first layer has no wifi info + assertThat(result).isNull() + } + private fun createAndSetRepo() { underTest = ConnectivityRepositoryImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index f69e9a39909b..d30e0246c2dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -29,6 +29,7 @@ import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.net.wifi.WifiManager.TrafficStateCallback +import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -49,16 +50,14 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import org.junit.After +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 @@ -80,9 +79,10 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var wifiManager: WifiManager private lateinit var executor: Executor - private lateinit var scope: CoroutineScope private lateinit var connectivityRepository: ConnectivityRepository + private val testScope = TestScope(UnconfinedTestDispatcher()) + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -96,7 +96,6 @@ class WifiRepositoryImplTest : SysuiTestCase() { ) .thenReturn(flowOf(Unit)) executor = FakeExecutor(FakeSystemClock()) - scope = CoroutineScope(IMMEDIATE) connectivityRepository = ConnectivityRepositoryImpl( @@ -105,21 +104,16 @@ class WifiRepositoryImplTest : SysuiTestCase() { context, mock(), mock(), - scope, + testScope.backgroundScope, mock(), ) underTest = createRepo() } - @After - fun tearDown() { - scope.cancel() - } - @Test fun isWifiEnabled_initiallyGetsWifiManagerValue() = - runBlocking(IMMEDIATE) { + testScope.runTest { whenever(wifiManager.isWifiEnabled).thenReturn(true) underTest = createRepo() @@ -129,7 +123,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { // We need to call launch on the flows so that they start updating val networkJob = underTest.wifiNetwork.launchIn(this) val enabledJob = underTest.isWifiEnabled.launchIn(this) @@ -152,7 +146,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_networkLost_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { // We need to call launch on the flows so that they start updating val networkJob = underTest.wifiNetwork.launchIn(this) val enabledJob = underTest.isWifiEnabled.launchIn(this) @@ -173,7 +167,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_intentsReceived_valueUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val intentFlow = MutableSharedFlow<Unit>() whenever( broadcastDispatcher.broadcastFlow( @@ -203,7 +197,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = - runBlocking(IMMEDIATE) { + testScope.runTest { val intentFlow = MutableSharedFlow<Unit>() whenever( broadcastDispatcher.broadcastFlow( @@ -242,7 +236,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_initiallyGetsDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) assertThat(underTest.isWifiDefault.value).isFalse() @@ -252,7 +246,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_wifiNetwork_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) } @@ -268,7 +262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val transportInfo = @@ -294,7 +288,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val transportInfo = @@ -319,7 +313,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaCellular_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val carrierMergedInfo = @@ -341,7 +335,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -360,7 +354,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaWifi_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val carrierMergedInfo = @@ -382,7 +376,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -400,8 +394,8 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test - fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() = - runBlocking(IMMEDIATE) { + fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() = + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -420,7 +414,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun isWifiDefault_cellularNotVcnNetwork_isFalse() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) val capabilities = @@ -437,8 +431,77 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() = + testScope.runTest { + val job = underTest.isWifiDefault.launchIn(this) + + val underlyingNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + } + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged, so wifi is default + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test + fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() = + testScope.runTest { + val job = underTest.isWifiDefault.launchIn(this) + + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) } + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged, so wifi is default + assertThat(underTest.isWifiDefault.value).isTrue() + + job.cancel() + } + + @Test fun isWifiDefault_wifiNetworkLost_isFalse() = - runBlocking(IMMEDIATE) { + testScope.runTest { val job = underTest.isWifiDefault.launchIn(this) // First, add a network @@ -457,7 +520,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_initiallyGetsDefault() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -468,7 +531,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -492,7 +555,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -511,8 +574,83 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() = + testScope.runTest { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val underlyingNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } + val underlyingWifiCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true) + whenever(it.transportInfo).thenReturn(carrierMergedInfo) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork)) + .thenReturn(underlyingWifiCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via WIFI + // transport and WifiInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + + job.cancel() + } + + @Test + fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() = + testScope.runTest { + var latest: WifiNetworkModel? = null + val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) + + val underlyingCarrierMergedNetwork = mock<Network>() + val carrierMergedInfo = + mock<WifiInfo>().apply { + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.isPrimary).thenReturn(true) + } + val underlyingCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo)) + } + whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork)) + .thenReturn(underlyingCapabilities) + + // WHEN the main capabilities have an underlying carrier merged network via CELLULAR + // transport and VcnTransportInfo + val mainCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true) + whenever(it.transportInfo).thenReturn(null) + whenever(it.underlyingNetworks) + .thenReturn(listOf(underlyingCarrierMergedNetwork)) + } + + getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities) + + // THEN the wifi network is carrier merged + assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue() + + job.cancel() + } + + @Test fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -536,7 +674,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_isCarrierMerged_getsCorrectValues() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -571,7 +709,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_notValidated_networkNotValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -588,7 +726,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_validated_networkValidated() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -605,7 +743,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -626,7 +764,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/266628069. */ @Test fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -645,7 +783,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -667,7 +805,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -691,7 +829,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -710,7 +848,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_cellularAndWifiTransports_usesCellular() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -733,7 +871,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -766,7 +904,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -799,7 +937,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_newNetworkCapabilities_flowHasNewData() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -842,7 +980,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -857,7 +995,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -876,7 +1014,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -899,7 +1037,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: WifiNetworkModel? = null val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this) @@ -925,7 +1063,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { /** Regression test for b/244173280. */ @Test fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest1: WifiNetworkModel? = null val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this) @@ -952,8 +1090,151 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun isWifiConnectedWithValidSsid_inactiveNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.ssid).thenReturn(SSID) + // A non-primary network is inactive + whenever(this.isPrimary).thenReturn(false) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_invalidNetwork_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.isCarrierMerged).thenReturn(true) + whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID) + } + + getNetworkCallback() + .onCapabilitiesChanged( + NETWORK, + createWifiNetworkCapabilities(wifiInfo), + ) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn(null) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn(UNKNOWN_SSID) + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn("FakeSsid") + } + + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + + job.cancel() + } + + @Test + fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() = + testScope.runTest { + val job = underTest.wifiNetwork.launchIn(this) + + // Start with active + val wifiInfo = + mock<WifiInfo>().apply { + whenever(this.isPrimary).thenReturn(true) + whenever(this.ssid).thenReturn("FakeSsid") + } + getNetworkCallback() + .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo)) + assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue() + + // WHEN the network is lost + getNetworkCallback().onLost(NETWORK) + + // THEN the isWifiConnected updates + assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse() + + job.cancel() + } + + @Test fun wifiActivity_callbackGivesNone_activityFlowHasNone() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -967,7 +1248,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesIn_activityFlowHasIn() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -981,7 +1262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesOut_activityFlowHasOut() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -995,7 +1276,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { @Test fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = - runBlocking(IMMEDIATE) { + testScope.runTest { var latest: DataActivityModel? = null val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this) @@ -1015,7 +1296,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { logger, tableLogger, executor, - scope, + testScope.backgroundScope, wifiManager, ) } @@ -1060,5 +1341,3 @@ class WifiRepositoryImplTest : SysuiTestCase() { } } } - -private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index ab4e93ceee84..4e0c309512e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model +import android.net.wifi.WifiManager.UNKNOWN_SSID import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -50,6 +51,42 @@ class WifiNetworkModelTest : SysuiTestCase() { WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1) } + @Test + fun active_hasValidSsid_nullSsid_false() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = null, + ) + + assertThat(network.hasValidSsid()).isFalse() + } + + @Test + fun active_hasValidSsid_unknownSsid_false() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = UNKNOWN_SSID, + ) + + assertThat(network.hasValidSsid()).isFalse() + } + + @Test + fun active_hasValidSsid_validSsid_true() { + val network = + WifiNetworkModel.Active( + NETWORK_ID, + level = MAX_VALID_LEVEL, + ssid = "FakeSsid", + ) + + assertThat(network.hasValidSsid()).isTrue() + } + // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index 1eee08c22187..91c88cebff79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -21,6 +21,7 @@ import static android.os.BatteryManager.EXTRA_PRESENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -167,8 +168,10 @@ public class BatteryControllerTest extends SysuiTestCase { mBatteryController.setPowerSaveMode(false, mView); StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true, + SAVER_ENABLED_QS)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true, + SAVER_ENABLED_QS)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 01e94baab7c3..391c8ca4d286 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; import androidx.annotation.NonNull; -import androidx.core.animation.AnimatorTestRule2; +import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @ClassRule - public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2(); + public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); @Before public void setUp() throws Exception { 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/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt new file mode 100644 index 000000000000..9383a0a68844 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.content.Context +import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.plugins.qs.QSTileView + +class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory { + override fun createTile(tileSpec: String): QSTile? { + return tileCreator(tileSpec) + } + + override fun createTileView( + context: Context?, + tile: QSTile?, + collapsedView: Boolean + ): QSTileView { + throw NotImplementedError("Not implemented") + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt new file mode 100644 index 000000000000..777130409aad --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.repository + +import android.content.ComponentName + +class FakeCustomTileAddedRepository : CustomTileAddedRepository { + + private val tileAddedRegistry = mutableSetOf<Pair<Int, ComponentName>>() + + override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean { + return (userId to componentName) in tileAddedRegistry + } + + override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) { + if (added) { + tileAddedRegistry.add(userId to componentName) + } else { + tileAddedRegistry.remove(userId to componentName) + } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt new file mode 100644 index 000000000000..2865710c2eae --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.pipeline.data.repository + +import android.util.Log +import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END +import com.android.systemui.qs.pipeline.shared.TileSpec +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeTileSpecRepository : TileSpecRepository { + + private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>() + + override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> { + return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") } + } + + override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) { + if (tile == TileSpec.Invalid) return + with(getFlow(userId)) { + value = + value.toMutableList().apply { + if (position == POSITION_AT_END) { + add(tile) + } else { + add(position, tile) + } + } + } + } + + override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) { + with(getFlow(userId)) { + value = + value.toMutableList().apply { removeAll(tiles.filter { it != TileSpec.Invalid }) } + } + } + + override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) { + getFlow(userId).value = tiles.filter { it != TileSpec.Invalid } + } + + private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> = + tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) } +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt index 380c1fcbf732..156d2fc2cb3a 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt @@ -25,14 +25,18 @@ import java.util.concurrent.Executor * It could be used when no activity context is available * TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873 */ -class ScreenSizeFoldProvider(private val context: Context) : FoldProvider { +class ScreenSizeFoldProvider(context: Context) : FoldProvider { + private var isFolded: Boolean = false private var callbacks: MutableList<FoldCallback> = arrayListOf() - private var lastWidth: Int = 0 + + init { + onConfigurationChange(context.resources.configuration) + } override fun registerCallback(callback: FoldCallback, executor: Executor) { callbacks += callback - onConfigurationChange(context.resources.configuration) + callback.onFoldUpdated(isFolded) } override fun unregisterCallback(callback: FoldCallback) { @@ -40,16 +44,14 @@ class ScreenSizeFoldProvider(private val context: Context) : FoldProvider { } fun onConfigurationChange(newConfig: Configuration) { - if (lastWidth == newConfig.smallestScreenWidthDp) { + val newIsFolded = + newConfig.smallestScreenWidthDp < INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + if (newIsFolded == isFolded) { return } - if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) { - callbacks.forEach { it.onFoldUpdated(false) } - } else { - callbacks.forEach { it.onFoldUpdated(true) } - } - lastWidth = newConfig.smallestScreenWidthDp + isFolded = newIsFolded + callbacks.forEach { it.onFoldUpdated(isFolded) } } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index a633a5e41882..46001a73e518 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -256,6 +256,12 @@ constructor( } } + override fun markScreenAsTurnedOn() { + if (!isFolded) { + isUnfoldHandled = true + } + } + override fun onScreenTurningOn() { isScreenOn = true updateHingeAngleProviderState() diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt index f09b53dc8436..2ee2940fa2a6 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt @@ -35,5 +35,12 @@ interface ScreenStatusProvider : CallbackController<ScreenListener> { * Called when the screen is starting to be turned on. */ fun onScreenTurningOn() + + /** + * Called when the screen is already turned on but it happened before the creation + * of the unfold progress provider, so we won't play the actual animation but we treat + * the current state of the screen as 'turned on' + */ + fun markScreenAsTurnedOn() } } 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/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8d039fc02026..2a964b8b701f 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -2073,15 +2073,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onSaveRequestSuccess(@NonNull String servicePackageName, @Nullable IntentSender intentSender) { - // Log onSaveRequest result. - mSaveEventLogger.maybeSetIsSaved(true); - final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); - mSaveEventLogger.logAndEndEvent(); - synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + // Log onSaveRequest result. + mSaveEventLogger.maybeSetIsSaved(true); + final long saveRequestFinishTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " + id + " destroyed"); @@ -2108,14 +2107,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull String servicePackageName) { boolean showMessage = !TextUtils.isEmpty(message); - // Log onSaveRequest result. - final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); - mSaveEventLogger.logAndEndEvent(); - synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + // Log onSaveRequest result. + final long saveRequestFinishTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " + id + " destroyed"); @@ -2228,8 +2226,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void save() { - mSaveEventLogger.maybeSetSaveButtonClicked(true); synchronized (mLock) { + mSaveEventLogger.maybeSetSaveButtonClicked(true); if (mDestroyed) { Slog.w(TAG, "Call to Session#save() rejected - session: " + id + " destroyed"); @@ -2247,10 +2245,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override public void cancelSave() { - mSaveEventLogger.maybeSetDialogDismissed(true); synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; - + mSaveEventLogger.maybeSetDialogDismissed(true); if (mDestroyed) { Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " + id + " destroyed"); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 542cc2f0a0a6..5b320a87d113 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; +import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; @@ -1066,6 +1067,10 @@ public class CompanionDeviceManagerService extends SystemService { // No role was granted to for this association, there is nothing else we need to here. return true; } + // Do not need to remove the system role since it was pre-granted by the system. + if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) { + return true; + } // Check if the applications is associated with another devices with the profile. If so, // it should remain the role holder. 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/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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 78cbf2bd80d8..7b618b11bd45 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -88,6 +88,7 @@ import static com.android.internal.messages.nano.SystemMessageProto.SystemMessag import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER; import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT; +import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT; import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED; import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL; import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED; @@ -3240,6 +3241,12 @@ public final class ActiveServices { return; } Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr); + final long now = SystemClock.uptimeMillis(); + logFGSStateChangeLocked(sr, + FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, + now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0, + FGS_STOP_REASON_UNKNOWN, + FGS_TYPE_POLICY_CHECK_UNKNOWN); try { sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId()); } catch (RemoteException e) { @@ -7897,7 +7904,8 @@ public final class ActiveServices { boolean allowWhileInUsePermissionInFgs; @PowerExemptionManager.ReasonCode int fgsStartReasonCode; if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER - || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) { + || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT + || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) { allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering; fgsStartReasonCode = r.mAllowStartForegroundAtEntering; } else { @@ -7931,9 +7939,9 @@ public final class ActiveServices { r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID, r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT, - 0, - null, - null); + 0 /* api_sate */, + null /* api_type */, + null /* api_timestamp */); int event = 0; if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) { @@ -7942,7 +7950,9 @@ public final class ActiveServices { event = EventLogTags.AM_FOREGROUND_SERVICE_STOP; } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) { event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED; - } else { + } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) { + event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT; + }else { // Unknown event. return; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ef7d5ae43396..9752ade1ce34 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13713,7 +13713,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver( /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) { throw new SecurityException("SDK sandbox not allowed to register receiver" - + " with the given IntentFilter: " + filter.toString()); + + " with the given IntentFilter: " + filter.toLongString()); } } 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/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index dbb351b23c85..bfc8251d97bb 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -441,17 +441,17 @@ class BroadcastProcessQueue { } public int getPreferredSchedulingGroupLocked() { - if (mCountForeground > mCountForegroundDeferred) { + if (!isActive()) { + return ProcessList.SCHED_GROUP_UNDEFINED; + } else if (mCountForeground > mCountForegroundDeferred) { // We have a foreground broadcast somewhere down the queue, so // boost priority until we drain them all return ProcessList.SCHED_GROUP_DEFAULT; } else if ((mActive != null) && mActive.isForeground()) { // We have a foreground broadcast right now, so boost priority return ProcessList.SCHED_GROUP_DEFAULT; - } else if (!isIdle()) { - return ProcessList.SCHED_GROUP_BACKGROUND; } else { - return ProcessList.SCHED_GROUP_UNDEFINED; + return ProcessList.SCHED_GROUP_BACKGROUND; } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 78edbba2e569..4328efe0224a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -178,6 +178,7 @@ public final class CachedAppOptimizer { private static final String ATRACE_FREEZER_TRACK = "Freezer"; private static final int FREEZE_BINDER_TIMEOUT_MS = 100; + private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000; @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false; @@ -244,6 +245,7 @@ public final class CachedAppOptimizer { static final int REPORT_UNFREEZE_MSG = 4; static final int COMPACT_NATIVE_MSG = 5; static final int UID_FROZEN_STATE_CHANGED_MSG = 6; + static final int DEADLOCK_WATCHDOG_MSG = 7; // When free swap falls below this percentage threshold any full (file + anon) // compactions will be downgraded to file only compactions to reduce pressure @@ -1327,7 +1329,7 @@ public final class CachedAppOptimizer { } try { - traceAppFreeze(app.processName, pid, false); + traceAppFreeze(app.processName, pid, reason); Process.setProcessFrozen(pid, app.uid, false); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); @@ -1339,7 +1341,7 @@ public final class CachedAppOptimizer { } if (!opt.isFrozen()) { - Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName); + Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason); mFreezeHandler.sendMessage( mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG, @@ -1369,7 +1371,7 @@ public final class CachedAppOptimizer { if (app == null) { return; } - Slog.d(TAG_AM, "quick sync unfreeze " + pid); + Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason); try { freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS); } catch (RuntimeException e) { @@ -1378,7 +1380,7 @@ public final class CachedAppOptimizer { } try { - traceAppFreeze(app.processName, pid, false); + traceAppFreeze(app.processName, pid, reason); Process.setProcessFrozen(pid, app.uid, false); } catch (Exception e) { Slog.e(TAG_AM, "Unable to quick unfreeze " + pid); @@ -1386,9 +1388,15 @@ public final class CachedAppOptimizer { } } - private static void traceAppFreeze(String processName, int pid, boolean freeze) { + /** + * Trace app freeze status + * @param processName The name of the target process + * @param pid The pid of the target process + * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing + */ + private static void traceAppFreeze(String processName, int pid, int reason) { Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK, - (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid); + (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason); } /** @@ -1947,29 +1955,15 @@ public final class CachedAppOptimizer { public void handleMessage(Message msg) { switch (msg.what) { case SET_FROZEN_PROCESS_MSG: - { ProcessRecord proc = (ProcessRecord) msg.obj; - int pid = proc.getPid(); - final String name = proc.processName; synchronized (mAm) { freezeProcess(proc); } - try { - // post-check to prevent deadlock - mProcLocksReader.handleBlockingFileLocks(this); - } catch (Exception e) { - Slog.e(TAG_AM, "Unable to check file locks for " - + name + "(" + pid + "): " + e); - synchronized (mAm) { - synchronized (mProcLock) { - unfreezeAppLSP(proc, UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE); - } - } - } if (proc.mOptRecord.isFrozen()) { onProcessFrozen(proc); + removeMessages(DEADLOCK_WATCHDOG_MSG); + sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS); } - } break; case REPORT_UNFREEZE_MSG: int pid = msg.arg1; @@ -1981,8 +1975,18 @@ public final class CachedAppOptimizer { reportUnfreeze(pid, frozenDuration, processName, reason); break; case UID_FROZEN_STATE_CHANGED_MSG: - ProcessRecord proc = (ProcessRecord) msg.obj; - reportOneUidFrozenStateChanged(proc.uid, true); + reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true); + break; + case DEADLOCK_WATCHDOG_MSG: + try { + // post-check to prevent deadlock + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, "Freezer deadlock watchdog"); + } + mProcLocksReader.handleBlockingFileLocks(this); + } catch (IOException e) { + Slog.w(TAG_AM, "Unable to check file locks"); + } break; default: return; @@ -2065,7 +2069,7 @@ public final class CachedAppOptimizer { long unfreezeTime = opt.getFreezeUnfreezeTime(); try { - traceAppFreeze(proc.processName, pid, true); + traceAppFreeze(proc.processName, pid, -1); Process.setProcessFrozen(pid, proc.uid, true); opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis()); @@ -2129,7 +2133,7 @@ public final class CachedAppOptimizer { private void reportUnfreeze(int pid, int frozenDuration, String processName, @UnfreezeReason int reason) { - EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName); + EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason); // See above for why we're not taking mPhenotypeFlagLock here if (mRandom.nextFloat() < mFreezerStatsdSampleRate) { diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index 81b242155bac..9e9db6aff699 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -121,10 +121,11 @@ option java_package com.android.server.am # Similarly, tags below are used by UserManagerService 30091 um_user_visibility_changed (userId|1|5),(visible|1) -# Foreground service start/stop events. +# Foreground service start/stop/denied/timed_out events. 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) 30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) 30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) +30103 am_foreground_service_timed_out (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1) # Intent Sender redirect for UserHandle.USER_CURRENT 30110 am_intent_sender_redirect_user (userId|1|5) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 60a7f9371837..8c227f5488d3 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -83,6 +83,7 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_CAMERA_NATIVE, DeviceConfig.NAMESPACE_CONFIGURATION, DeviceConfig.NAMESPACE_CONNECTIVITY, + DeviceConfig.NAMESPACE_EDGETPU_NATIVE, DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT, DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS, DeviceConfig.NAMESPACE_LMKD_NATIVE, @@ -99,7 +100,6 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, - DeviceConfig.NAMESPACE_TETHERING, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java index 937e3f8f8668..bac44809883f 100644 --- a/services/core/java/com/android/server/biometrics/BiometricSensor.java +++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java @@ -22,14 +22,20 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; +import android.hardware.biometrics.SensorPropertiesInternal; import android.os.IBinder; import android.os.RemoteException; +import android.text.TextUtils; +import android.util.IndentingPrintWriter; import android.util.Slog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.List; /** * Wraps IBiometricAuthenticator implementation and stores information about the authenticator, @@ -67,6 +73,7 @@ public abstract class BiometricSensor { public final int id; public final @Authenticators.Types int oemStrength; // strength as configured by the OEM public final int modality; + @NonNull public final List<ComponentInfoInternal> componentInfo; public final IBiometricAuthenticator impl; private @Authenticators.Types int mUpdatedStrength; // updated by BiometricStrengthController @@ -86,15 +93,16 @@ public abstract class BiometricSensor { */ abstract boolean confirmationSupported(); - BiometricSensor(@NonNull Context context, int id, int modality, - @Authenticators.Types int strength, IBiometricAuthenticator impl) { + BiometricSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props, + IBiometricAuthenticator impl) { this.mContext = context; - this.id = id; + this.id = props.sensorId; this.modality = modality; - this.oemStrength = strength; + this.oemStrength = Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); + this.componentInfo = Collections.unmodifiableList(props.componentInfo); this.impl = impl; - mUpdatedStrength = strength; + mUpdatedStrength = oemStrength; goToStateUnknown(); } @@ -178,8 +186,25 @@ public abstract class BiometricSensor { return "ID(" + id + ")" + ", oemStrength: " + oemStrength + ", updatedStrength: " + mUpdatedStrength - + ", modality " + modality + + ", modality: " + modality + ", state: " + mSensorState + ", cookie: " + mCookie; } + + protected void dump(@NonNull IndentingPrintWriter pw) { + pw.println(TextUtils.formatSimple("ID: %d", id)); + pw.increaseIndent(); + pw.println(TextUtils.formatSimple("oemStrength: %d", oemStrength)); + pw.println(TextUtils.formatSimple("updatedStrength: %d", mUpdatedStrength)); + pw.println(TextUtils.formatSimple("modality: %d", modality)); + pw.println("componentInfo:"); + for (ComponentInfoInternal info : componentInfo) { + pw.increaseIndent(); + info.dump(pw); + pw.decreaseIndent(); + } + pw.println(TextUtils.formatSimple("state: %d", mSensorState)); + pw.println(TextUtils.formatSimple("cookie: %d", mCookie)); + pw.decreaseIndent(); + } } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index ffa5d2055e92..f44d14bfa12c 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -62,6 +62,7 @@ import android.provider.Settings; import android.security.KeyStore; import android.text.TextUtils; import android.util.ArraySet; +import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -638,13 +639,16 @@ public class BiometricService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override - public synchronized void registerAuthenticator(int id, int modality, - @Authenticators.Types int strength, + public synchronized void registerAuthenticator(int modality, + @NonNull SensorPropertiesInternal props, @NonNull IBiometricAuthenticator authenticator) { super.registerAuthenticator_enforcePermission(); - Slog.d(TAG, "Registering ID: " + id + @Authenticators.Types final int strength = + Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); + + Slog.d(TAG, "Registering ID: " + props.sensorId + " Modality: " + modality + " Strength: " + strength); @@ -665,12 +669,12 @@ public class BiometricService extends SystemService { } for (BiometricSensor sensor : mSensors) { - if (sensor.id == id) { + if (sensor.id == props.sensorId) { throw new IllegalStateException("Cannot register duplicate authenticator"); } } - mSensors.add(new BiometricSensor(getContext(), id, modality, strength, authenticator) { + mSensors.add(new BiometricSensor(getContext(), modality, props, authenticator) { @Override boolean confirmationAlwaysRequired(int userId) { return mSettingObserver.getConfirmationAlwaysRequired(modality, userId); @@ -1360,13 +1364,17 @@ public class BiometricService extends SystemService { return null; } - private void dumpInternal(PrintWriter pw) { + private void dumpInternal(PrintWriter printWriter) { + IndentingPrintWriter pw = new IndentingPrintWriter(printWriter); + pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings); pw.println(); pw.println("Sensors:"); for (BiometricSensor sensor : mSensors) { - pw.println(" " + sensor); + pw.increaseIndent(); + sensor.dump(pw); + pw.decreaseIndent(); } pw.println(); pw.println("CurrentSession: " + mAuthSession); diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java index 0f0a81d24473..d43045b4450f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java @@ -20,7 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import android.annotation.NonNull; import android.annotation.Nullable; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.IBiometricService; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; @@ -28,7 +27,6 @@ import android.hardware.face.IFaceService; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BiometricServiceRegistry; import java.util.List; @@ -53,10 +51,8 @@ public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvide @Override protected void registerService(@NonNull IBiometricService service, @NonNull FaceSensorPropertiesInternal props) { - @BiometricManager.Authenticators.Types final int strength = - Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); try { - service.registerAuthenticator(props.sensorId, TYPE_FACE, strength, + service.registerAuthenticator(TYPE_FACE, props, new FaceAuthenticator(mService, props.sensorId)); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java index 33810b764f23..6d210eac542b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java @@ -20,7 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import android.annotation.NonNull; import android.annotation.Nullable; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.IBiometricService; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; @@ -28,7 +27,6 @@ import android.hardware.fingerprint.IFingerprintService; import android.os.RemoteException; import android.util.Slog; -import com.android.server.biometrics.Utils; import com.android.server.biometrics.sensors.BiometricServiceRegistry; import java.util.List; @@ -53,10 +51,8 @@ public class FingerprintServiceRegistry extends BiometricServiceRegistry<Service @Override protected void registerService(@NonNull IBiometricService service, @NonNull FingerprintSensorPropertiesInternal props) { - @BiometricManager.Authenticators.Types final int strength = - Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength); try { - service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength, + service.registerAuthenticator(TYPE_FINGERPRINT, props, new FingerprintAuthenticator(mService, props.sensorId)); } catch (RemoteException e) { Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId); diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java index 35ea36c5d56f..f27761fd644c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java @@ -16,12 +16,10 @@ package com.android.server.biometrics.sensors.iris; -import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS; import android.annotation.NonNull; import android.content.Context; -import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.IBiometricService; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.iris.IIrisService; @@ -33,7 +31,6 @@ import android.util.Slog; import com.android.server.ServiceThread; import com.android.server.SystemService; -import com.android.server.biometrics.Utils; import java.util.List; @@ -75,17 +72,12 @@ public class IrisService extends SystemService { ServiceManager.getService(Context.BIOMETRIC_SERVICE)); for (SensorPropertiesInternal hidlSensor : hidlSensors) { - final int sensorId = hidlSensor.sensorId; - final @BiometricManager.Authenticators.Types int strength = - Utils.propertyStrengthToAuthenticatorStrength( - hidlSensor.sensorStrength); - final IrisAuthenticator authenticator = new IrisAuthenticator(mServiceWrapper, - sensorId); try { - biometricService.registerAuthenticator(sensorId, TYPE_IRIS, strength, - authenticator); + biometricService.registerAuthenticator(TYPE_IRIS, hidlSensor, + new IrisAuthenticator(mServiceWrapper, hidlSensor.sensorId)); } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId); + Slog.e(TAG, "Remote exception when registering sensorId: " + + hidlSensor.sensorId); } } }); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b25206d3b621..7e48f68dcefc 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -3391,6 +3391,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onDefaultNetworkChanged(@NonNull Network network) { + mEventChanges.log("[UnderlyingNW] Default network changed to " + network); Log.d(TAG, "onDefaultNetworkChanged: " + network); // If there is a new default network brought up, cancel the retry task to prevent @@ -3628,6 +3629,7 @@ public class Vpn { mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities) .setTransportInfo(info) .build(); + mEventChanges.log("[VPNRunner] Update agent caps " + mNetworkCapabilities); doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities); } } @@ -3664,6 +3666,7 @@ public class Vpn { private void startIkeSession(@NonNull Network underlyingNetwork) { Log.d(TAG, "Start new IKE session on network " + underlyingNetwork); + mEventChanges.log("[IKE] Start IKE session over " + underlyingNetwork); try { // Clear mInterface to prevent Ikev2VpnRunner being cleared when @@ -3778,6 +3781,7 @@ public class Vpn { } public void onValidationStatus(int status) { + mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { @@ -3818,6 +3822,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onDefaultNetworkLost(@NonNull Network network) { + mEventChanges.log("[UnderlyingNW] Network lost " + network); // If the default network is torn down, there is no need to call // startOrMigrateIkeSession() since it will always check if there is an active network // can be used or not. @@ -3936,6 +3941,8 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ public void onSessionLost(int token, @Nullable Exception exception) { + mEventChanges.log("[IKE] Session lost on network " + mActiveNetwork + + (null == exception ? "" : " reason " + exception.getMessage())); Log.d(TAG, "onSessionLost() called for token " + token); if (!isActiveToken(token)) { @@ -4092,6 +4099,7 @@ public class Vpn { * consistency of the Ikev2VpnRunner fields. */ private void disconnectVpnRunner() { + mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork); mActiveNetwork = null; mUnderlyingNetworkCapabilities = null; mUnderlyingLinkProperties = null; diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index 067a2d6d8b0d..cfdcd636904b 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -36,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; @@ -69,12 +69,12 @@ class BrightnessThrottler { // Maps the throttling ID to the data. Sourced from DisplayDeviceConfig. @NonNull - private HashMap<String, BrightnessThrottlingData> mDdcThrottlingDataMap; + private HashMap<String, ThermalBrightnessThrottlingData> mDdcThermalThrottlingDataMap; // Current throttling data being used. // Null if we do not support throttling. @Nullable - private BrightnessThrottlingData mThrottlingData; + private ThermalBrightnessThrottlingData mThermalThrottlingData; private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = @@ -82,51 +82,53 @@ class BrightnessThrottler { 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 mBrightnessThrottlingDataId; + 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. // 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, BrightnessThrottlingData >> - private final HashMap<String, HashMap<String, BrightnessThrottlingData>> - mBrightnessThrottlingDataOverride = new HashMap<>(1); + // 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, BrightnessThrottlingData> brightnessThrottlingDataMap) { + @NonNull HashMap<String, ThermalBrightnessThrottlingData> + thermalBrightnessThrottlingDataMap) { this(new Injector(), handler, handler, throttlingChangeCallback, - uniqueDisplayId, throttlingDataId, brightnessThrottlingDataMap); + uniqueDisplayId, throttlingDataId, thermalBrightnessThrottlingDataMap); } @VisibleForTesting BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, Runnable throttlingChangeCallback, String uniqueDisplayId, String throttlingDataId, - @NonNull HashMap<String, BrightnessThrottlingData> brightnessThrottlingDataMap) { + @NonNull HashMap<String, ThermalBrightnessThrottlingData> + thermalBrightnessThrottlingDataMap) { mInjector = injector; mHandler = handler; mDeviceConfigHandler = deviceConfigHandler; - mDdcThrottlingDataMap = brightnessThrottlingDataMap; + mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; mThrottlingChangeCallback = throttlingChangeCallback; mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mUniqueDisplayId = uniqueDisplayId; mDeviceConfig = injector.getDeviceConfig(); mDeviceConfigListener = new DeviceConfigListener(); - mBrightnessThrottlingDataId = throttlingDataId; - mDdcThrottlingDataMap = brightnessThrottlingDataMap; - loadBrightnessThrottlingDataFromDeviceConfig(); - loadBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThrottlingDataMap, - mBrightnessThrottlingDataId, mUniqueDisplayId); + mThermalBrightnessThrottlingDataId = throttlingDataId; + mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; + loadThermalBrightnessThrottlingDataFromDeviceConfig(); + loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig(mDdcThermalThrottlingDataMap, + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } boolean deviceSupportsThrottling() { - return mThrottlingData != null; + return mThermalThrottlingData != null; } float getBrightnessCap() { @@ -154,14 +156,14 @@ class BrightnessThrottler { mThrottlingStatus = THROTTLING_INVALID; } - void loadBrightnessThrottlingDataFromDisplayDeviceConfig( - HashMap<String, BrightnessThrottlingData> ddcThrottlingDataMap, + void loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + HashMap<String, ThermalBrightnessThrottlingData> ddcThrottlingDataMap, String brightnessThrottlingDataId, String uniqueDisplayId) { - mDdcThrottlingDataMap = ddcThrottlingDataMap; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; + mDdcThermalThrottlingDataMap = ddcThrottlingDataMap; + mThermalBrightnessThrottlingDataId = brightnessThrottlingDataId; mUniqueDisplayId = uniqueDisplayId; - resetThrottlingData(); + resetThermalThrottlingData(); } private float verifyAndConstrainBrightnessCap(float brightness) { @@ -183,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; } @@ -195,9 +197,9 @@ class BrightnessThrottler { float brightnessCap = PowerManager.BRIGHTNESS_MAX; int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; - if (mThrottlingStatus != THROTTLING_INVALID && mThrottlingData != null) { + 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; @@ -230,21 +232,23 @@ class BrightnessThrottler { private void dumpLocal(PrintWriter pw) { pw.println("BrightnessThrottler:"); - pw.println(" mBrightnessThrottlingDataId=" + mBrightnessThrottlingDataId); - pw.println(" mThrottlingData=" + mThrottlingData); + 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(" mDdcThrottlingDataMap=" + mDdcThrottlingDataMap); - 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); @@ -260,7 +264,7 @@ class BrightnessThrottler { // displayId, number, <state, val> * number // displayId, <number, <state, val> * number>, throttlingId private boolean parseAndAddData(@NonNull String strArray, - @NonNull HashMap<String, HashMap<String, BrightnessThrottlingData>> + @NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> displayIdToThrottlingIdToBtd) { boolean validConfig = true; String[] items = strArray.split(","); @@ -282,11 +286,11 @@ class BrightnessThrottler { } String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID; - BrightnessThrottlingData throttlingLevelsData = - DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels); + ThermalBrightnessThrottlingData throttlingLevelsData = + DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels); // Add throttlingLevelsData to inner map where necessary. - HashMap<String, BrightnessThrottlingData> throttlingMapForDisplay = + HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay = displayIdToThrottlingIdToBtd.get(uniqueDisplayId); if (throttlingMapForDisplay == null) { throttlingMapForDisplay = new HashMap<>(); @@ -311,14 +315,14 @@ class BrightnessThrottler { return validConfig; } - private void loadBrightnessThrottlingDataFromDeviceConfig() { - HashMap<String, HashMap<String, BrightnessThrottlingData>> tempThrottlingData = + 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 (!parseAndAddData(s, tempThrottlingData)) { validConfig = false; @@ -327,26 +331,27 @@ class BrightnessThrottler { } if (validConfig) { - mBrightnessThrottlingDataOverride.putAll(tempThrottlingData); + mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData); tempThrottlingData.clear(); } } else { - Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null"); + Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null"); } } - private void resetThrottlingData() { + private void resetThermalThrottlingData() { stop(); mDeviceConfigListener.startListening(); // Get throttling data for this id, if it exists - mThrottlingData = getConfigFromId(mBrightnessThrottlingDataId); + mThermalThrottlingData = getConfigFromId(mThermalBrightnessThrottlingDataId); // Fallback to default id otherwise. - if (!DEFAULT_ID.equals(mBrightnessThrottlingDataId) && mThrottlingData == null) { - mThrottlingData = getConfigFromId(DEFAULT_ID); + if (!DEFAULT_ID.equals(mThermalBrightnessThrottlingDataId) + && mThermalThrottlingData == null) { + mThermalThrottlingData = getConfigFromId(DEFAULT_ID); Slog.d(TAG, "Falling back to default throttling Id"); } @@ -355,17 +360,17 @@ class BrightnessThrottler { } } - private BrightnessThrottlingData getConfigFromId(String id) { - BrightnessThrottlingData returnValue; + 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 = mBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null + returnValue = mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId) == null ? null - : mBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id); + : mThermalBrightnessThrottlingDataOverride.get(mUniqueDisplayId).get(id); // 2) throttling data from ddc for this throttling data id returnValue = returnValue == null - ? mDdcThrottlingDataMap.get(id) + ? mDdcThermalThrottlingDataMap.get(id) : returnValue; return returnValue; @@ -391,8 +396,8 @@ class BrightnessThrottler { @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { - loadBrightnessThrottlingDataFromDeviceConfig(); - 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 da021158eb65..a021174e3031 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -687,8 +687,8 @@ public class DisplayDeviceConfig { private int[] mHighDisplayBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; private int[] mHighAmbientBrightnessThresholds = DEFAULT_BRIGHTNESS_THRESHOLDS; - private final HashMap<String, BrightnessThrottlingData> - mBrightnessThrottlingDataMapByThrottlingId = new HashMap<>(); + private final HashMap<String, ThermalBrightnessThrottlingData> + mThermalBrightnessThrottlingDataMapByThrottlingId = new HashMap<>(); private final Map<String, SparseArray<SurfaceControl.RefreshRateRange>> mRefreshRateThrottlingMap = new HashMap<>(); @@ -1348,9 +1348,9 @@ public class DisplayDeviceConfig { /** * @return brightness throttling configuration data for this display, for each throttling id. */ - public HashMap<String, BrightnessThrottlingData> - getBrightnessThrottlingDataMapByThrottlingId() { - return mBrightnessThrottlingDataMapByThrottlingId; + 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,8 +1525,8 @@ public class DisplayDeviceConfig { + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline - + ", mBrightnessThrottlingDataMapByThrottlingId=" - + mBrightnessThrottlingDataMapByThrottlingId + + ", mThermalBrightnessThrottlingDataMapByThrottlingId=" + + mThermalBrightnessThrottlingDataMapByThrottlingId + "\n" + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease @@ -1887,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"); @@ -1901,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; @@ -1912,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 (mBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) { + if (mThermalBrightnessThrottlingDataMapByThrottlingId.containsKey(id)) { throw new RuntimeException("Brightness throttling data with ID " + id + " already exists"); } - mBrightnessThrottlingDataMapByThrottlingId.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"); @@ -3039,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 { @@ -3080,7 +3080,8 @@ public class DisplayDeviceConfig { /** * 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; @@ -3118,12 +3119,12 @@ public class DisplayDeviceConfig { } } - return new BrightnessThrottlingData(throttlingLevels); + return new ThermalBrightnessThrottlingData(throttlingLevels); } @Override public String toString() { - return "BrightnessThrottlingData{" + return "ThermalBrightnessThrottlingData{" + "throttlingLevels:" + throttlingLevels + "} "; } @@ -3134,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 @@ -3148,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 2322e03d6d37..5e3990ac7167 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -515,8 +515,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mIsEnabled; private boolean mIsInTransition; - // The id of the brightness throttling policy that should be used. - 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. @@ -556,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(); @@ -891,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) { @@ -901,7 +902,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; loadFromDisplayDeviceConfig(token, info, hbmMetadata); loadNitBasedBrightnessSetting(); @@ -909,12 +910,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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.loadBrightnessThrottlingDataFromDisplayDeviceConfig( - config.getBrightnessThrottlingDataMapByThrottlingId(), - mBrightnessThrottlingDataId, + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + config.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { @@ -983,9 +985,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call sdrBrightness, maxDesiredHdrSdrRatio); } }); - mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig( - mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId(), - mBrightnessThrottlingDataId, mUniqueDisplayId); + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } private void sendUpdatePowerState() { @@ -2121,8 +2123,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call () -> { sendUpdatePowerState(); postBrightnessChangeRunnable(); - }, mUniqueDisplayId, mLogicalDisplay.getBrightnessThrottlingDataIdLocked(), - ddConfig.getBrightnessThrottlingDataMapByThrottlingId()); + }, 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 d2af88b1c6c9..23e606c028ac 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -400,8 +400,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal private boolean mIsEnabled; private boolean mIsInTransition; - // The id of the brightness throttling policy that should be used. - 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. @@ -439,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(); @@ -707,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; @@ -718,7 +719,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mUniqueDisplayId = uniqueId; mDisplayStatsId = mUniqueDisplayId.hashCode(); mDisplayDeviceConfig = config; - mBrightnessThrottlingDataId = brightnessThrottlingDataId; + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; loadFromDisplayDeviceConfig(token, info, hbmMetadata); mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(config); @@ -726,12 +727,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // 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.loadBrightnessThrottlingDataFromDisplayDeviceConfig( - config.getBrightnessThrottlingDataMapByThrottlingId(), - mBrightnessThrottlingDataId, + mThermalBrightnessThrottlingDataId = thermalBrightnessThrottlingDataId; + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + config.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { @@ -797,9 +799,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal sdrBrightness, maxDesiredHdrSdrRatio); } }); - mBrightnessThrottler.loadBrightnessThrottlingDataFromDisplayDeviceConfig( - mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId(), - mBrightnessThrottlingDataId, mUniqueDisplayId); + mBrightnessThrottler.loadThermalBrightnessThrottlingDataFromDisplayDeviceConfig( + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId(), + mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } private void sendUpdatePowerState() { @@ -1762,8 +1764,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal () -> { sendUpdatePowerState(); postBrightnessChangeRunnable(); - }, mUniqueDisplayId, mLogicalDisplay.getBrightnessThrottlingDataIdLocked(), - ddConfig.getBrightnessThrottlingDataMapByThrottlingId()); + }, 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 634f31d6268c..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,12 +338,12 @@ public class Layout { } /** - * Gets the id of the brightness throttling map that should be used. - * @return The ID of the brightness throttling map that this display should use, null if - * unspecified, will fall back to default. + * 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; } /** @@ -361,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/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index c05a03ee1d2d..c76ca2beda96 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -286,7 +286,6 @@ final class AdditionalSubtypeUtils { final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(label) - .setSubtypeNameOverride(untranslatableName) .setPhysicalKeyboardHint( pkLanguageTag == null ? null : new ULocale(pkLanguageTag), pkLayoutType == null ? "" : pkLayoutType) @@ -302,6 +301,9 @@ final class AdditionalSubtypeUtils { if (subtypeId != InputMethodSubtype.SUBTYPE_ID_NONE) { builder.setSubtypeId(subtypeId); } + if (untranslatableName != null) { + builder.setSubtypeNameOverride(untranslatableName); + } tempSubtypesArray.add(builder.build()); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index c212e8e3c82c..44ae454e7ef2 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -19,7 +19,6 @@ package com.android.server.inputmethod; import static com.android.server.inputmethod.InputMethodManagerService.DEBUG; import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID; -import android.annotation.Nullable; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -66,8 +65,8 @@ final class InputMethodMenuController { private boolean mShowImeWithHardKeyboard; @GuardedBy("ImfLock.class") - @Nullable - private InputMethodDialogWindowContext mDialogWindowContext; + private final InputMethodDialogWindowContext mDialogWindowContext = + new InputMethodDialogWindowContext(); InputMethodMenuController(InputMethodManagerService service) { mService = service; @@ -125,13 +124,11 @@ final class InputMethodMenuController { } } - if (mDialogWindowContext == null) { - mDialogWindowContext = new InputMethodDialogWindowContext(); - } final Context dialogWindowContext = mDialogWindowContext.get(displayId); mDialogBuilder = new AlertDialog.Builder(dialogWindowContext); mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu()); + // TODO(b/277061090): refactor UI components should not be created while holding a lock. final Context dialogContext = mDialogBuilder.getContext(); final TypedArray a = dialogContext.obtainStyledAttributes(null, com.android.internal.R.styleable.DialogPreference, @@ -199,10 +196,11 @@ final class InputMethodMenuController { attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; attrs.setTitle("Select input method"); w.setAttributes(attrs); + // TODO(b/277062834) decouple/remove dependency on IMMS mService.updateSystemUiLocked(); mService.sendOnNavButtonFlagsChangedLocked(); - mSwitchingDialog.show(); } + mSwitchingDialog.show(); } private boolean isScreenLocked() { @@ -276,6 +274,7 @@ final class InputMethodMenuController { private final int mTextViewResourceId; private final List<ImeSubtypeListItem> mItemsList; public int mCheckedItem; + private ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem) { super(context, textViewResourceId, itemsList); diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 43e346a5bfa3..2d4066144a7f 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -323,7 +323,7 @@ public class LocaleManagerService extends SystemService { */ void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, appPackageName, locales); @@ -464,7 +464,7 @@ public class LocaleManagerService extends SystemService { * Checks if the calling app is the installer of the app whose locale changed. */ private boolean isCallerInstaller(String appPackageName, int userId) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { // Get the uid of installer-on-record to compare with the calling uid. int installerUid = getPackageUid(installingPackageName, userId); @@ -513,10 +513,11 @@ public class LocaleManagerService extends SystemService { } @Nullable - String getInstallingPackageName(String packageName) { + String getInstallingPackageName(String packageName, int userId) { try { - return mContext.getPackageManager() - .getInstallSourceInfo(packageName).getInstallingPackageName(); + return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ + 0).getPackageManager().getInstallSourceInfo( + packageName).getInstallingPackageName(); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package not found " + packageName); } diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java index 215c653f1be7..373d3553e0eb 100644 --- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java +++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java @@ -152,9 +152,10 @@ public class SystemAppUpdateTracker { void onPackageUpdateFinished(String packageName, int uid) { try { if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) { + int userId = UserHandle.getUserId(uid); // If a system app is updated, verify that it has an installer-on-record. String installingPackageName = mLocaleManagerService.getInstallingPackageName( - packageName); + packageName, userId); if (installingPackageName == null) { // We want to broadcast the locales info to the installer. // If this app does not have an installer then do nothing. @@ -162,7 +163,6 @@ public class SystemAppUpdateTracker { } try { - int userId = UserHandle.getUserId(uid); // Fetch the app-specific locales. // If non-empty then send the info to the installer. LocaleList appLocales = mLocaleManagerService.getApplicationLocales( diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 0da94ff6aa0f..ee4a6fea8ad4 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1516,11 +1516,6 @@ public class LockSettingsService extends ILockSettings.Stub { && !getSeparateProfileChallengeEnabledInternal(userId); } - private boolean isProfileWithSeparatedLock(int userId) { - return isCredentialSharableWithParent(userId) - && getSeparateProfileChallengeEnabledInternal(userId); - } - /** * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an * unlock operation. @@ -2784,9 +2779,19 @@ public class LockSettingsService extends ILockSettings.Stub { activateEscrowTokens(sp, userId); - if (isProfileWithSeparatedLock(userId)) { - setDeviceUnlockedForUser(userId); + if (isCredentialSharableWithParent(userId)) { + if (getSeparateProfileChallengeEnabledInternal(userId)) { + setDeviceUnlockedForUser(userId); + } else { + // Here only clear StrongAuthFlags for a profile that has a unified challenge. + // StrongAuth for a profile with a separate challenge is handled differently and + // is cleared after the user successfully confirms the separate challenge to enter + // the profile. StrongAuth for the full user (e.g. userId 0) is also handled + // separately by Keyguard. + mStrongAuth.reportUnlock(userId); + } } + mStrongAuth.reportSuccessfulStrongAuthUnlock(userId); onSyntheticPasswordUnlocked(userId, sp); diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index f0e8ede5987d..94d5aabe24e5 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -357,12 +357,16 @@ public final class MediaProjectionManagerService extends SystemService } catch (NameNotFoundException e) { throw new IllegalArgumentException("No package matching :" + packageName); } - - projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion, - ai.isPrivilegedApp()); - if (isPermanentGrant) { - mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, - projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); + final long callingToken = Binder.clearCallingIdentity(); + try { + projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion, + ai.isPrivilegedApp()); + if (isPermanentGrant) { + mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA, + projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED); + } + } finally { + Binder.restoreCallingIdentity(callingToken); } return projection; } @@ -418,16 +422,9 @@ public final class MediaProjectionManagerService extends SystemService if (packageName == null || packageName.isEmpty()) { throw new IllegalArgumentException("package name must not be empty"); } - MediaProjection projection; final UserHandle callingUser = Binder.getCallingUserHandle(); - final long callingToken = Binder.clearCallingIdentity(); - try { - projection = createProjectionInternal(uid, packageName, type, isPermanentGrant, - callingUser, false); - } finally { - Binder.restoreCallingIdentity(callingToken); - } - return projection; + return createProjectionInternal(uid, packageName, type, isPermanentGrant, + callingUser, false); } @Override // Binder call 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 87070595f172..5d81dda5f5eb 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 @@ -3806,6 +3815,28 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean canUseFullScreenIntent(@NonNull AttributionSource attributionSource) { + final String packageName = attributionSource.getPackageName(); + final int uid = attributionSource.getUid(); + final int userId = UserHandle.getUserId(uid); + checkCallerIsSameApp(packageName, uid, userId); + + final ApplicationInfo applicationInfo; + try { + applicationInfo = mPackageManagerClient.getApplicationInfoAsUser( + packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId); + } catch (NameNotFoundException e) { + Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e); + return false; + } + final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags + .SHOW_STICKY_HUN_FOR_DENIED_FSI); + return checkUseFullScreenIntentPermission(attributionSource, applicationInfo, + showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */); + } + + @Override public void updateNotificationChannelGroupForPackage(String pkg, int uid, NotificationChannelGroup group) throws RemoteException { enforceSystemOrSystemUI("Caller not system or systemui"); @@ -3964,6 +3995,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 +4019,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 +4064,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 +6408,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 +6550,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 +6607,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 +6625,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,26 +6683,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). + // 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 (!fgServiceShown) { + if (!channel.isUserVisibleTaskShown()) { channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); - channel.setFgServiceShown(true); + channel.setUserVisibleTaskShown(true); } mPreferencesHelper.updateNotificationChannel( pkg, notificationUid, channel, false); r.updateNotificationChannel(channel); - } else if (!fgServiceShown && !TextUtils.isEmpty(channelId) + } else if (!channel.isUserVisibleTaskShown() && !TextUtils.isEmpty(channelId) && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { - channel.setFgServiceShown(true); + channel.setUserVisibleTaskShown(true); r.updateNotificationChannel(channel); } } @@ -6729,7 +6794,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, @@ -6739,6 +6805,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)) { @@ -6774,36 +6848,28 @@ public class NotificationManagerService extends SystemService { notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; - if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) { + if (notification.fullScreenIntent != null) { final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled( SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE); - - final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( - SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI); - if (forceDemoteFsiToStickyHun) { makeStickyHun(notification, pkg, userId); - - } else if (showStickyHunIfDenied) { - final AttributionSource source = new AttributionSource.Builder(notificationUid) - .setPackageName(pkg) - .build(); - - final int permissionResult = mPermissionManager.checkPermissionForDataDelivery( - Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null); - - if (permissionResult != PermissionManager.PERMISSION_GRANTED) { - makeStickyHun(notification, pkg, userId); - } - } else { - int fullscreenIntentPermission = getContext().checkPermission( - android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid); - - if (fullscreenIntentPermission != PERMISSION_GRANTED) { - notification.fullScreenIntent = null; - Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" - + "USE_FULL_SCREEN_INTENT permission"); + final AttributionSource attributionSource = + new AttributionSource.Builder(notificationUid).setPackageName(pkg).build(); + final boolean showStickyHunIfDenied = mFlagResolver.isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags + .SHOW_STICKY_HUN_FOR_DENIED_FSI); + final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission( + attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */, + true /* forDataDelivery */); + if (!canUseFullScreenIntent) { + if (showStickyHunIfDenied) { + makeStickyHun(notification, pkg, userId); + } else { + notification.fullScreenIntent = null; + Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the" + + "USE_FULL_SCREEN_INTENT permission"); + } } } } @@ -6899,6 +6965,30 @@ public class NotificationManagerService extends SystemService { ai.packageName) == AppOpsManager.MODE_ALLOWED; } + private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource, + @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission, + boolean forDataDelivery) { + if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) { + return true; + } + if (isAppOpPermission) { + final int permissionResult; + if (forDataDelivery) { + permissionResult = mPermissionManager.checkPermissionForDataDelivery( + permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null); + } else { + permissionResult = mPermissionManager.checkPermissionForPreflight( + permission.USE_FULL_SCREEN_INTENT, attributionSource); + } + return permissionResult == PermissionManager.PERMISSION_GRANTED; + } else { + final int permissionResult = getContext().checkPermission( + permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(), + attributionSource.getUid()); + return permissionResult == PERMISSION_GRANTED; + } + } + private void checkRemoteViews(String pkg, String tag, int id, Notification notification) { if (removeRemoteView(pkg, tag, id, notification.contentView)) { notification.contentView = null; @@ -7083,8 +7173,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); @@ -7444,7 +7534,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; } } @@ -7997,7 +8088,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/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java index cae7079c75e0..82b585d7ad01 100644 --- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java +++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java @@ -65,7 +65,7 @@ public class DynamicCodeLoggingService extends JobService { private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " "; private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN = - Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*" + Pattern.compile(".*\\bavc: +granted +\\{ execute(?:_no_trans|) \\} .*" + "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*" + "\\bscontext=u:r:untrusted_app(?:_25|_27)?:.*" + "\\btcontext=u:object_r:app_data_file:.*" diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java index c29e4d78f929..52fdbda04fcd 100644 --- a/services/core/java/com/android/server/pm/IPackageManagerBase.java +++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java @@ -606,6 +606,7 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub { final Computer snapshot = snapshot(); // Return null for InstantApps. if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) { + Log.w(PackageManagerService.TAG, "Returning null PackageInstaller for InstantApps"); return null; } return mInstallerService; diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 5f424edb15c4..596e9b964643 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3588,6 +3588,11 @@ final class InstallPackageHelper { // remove the package from the system and re-scan it without any // special privileges mRemovePackageHelper.removePackage(pkg, true); + PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); + if (ps != null) { + ps.getPkgState().setUpdatedSystemApp(false); + } + try { final File codePath = new File(pkg.getPath()); synchronized (mPm.mInstallLock) { 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 048391eea292..006d7c82398a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1456,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); 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/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index ad77ef7ca975..9127a93a46ee 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -187,6 +187,9 @@ public final class SuspendPackageHelper { if (changed) { changedPackagesList.add(packageName); changedUids.add(UserHandle.getUid(userId, packageState.getAppId())); + } else { + Slog.w(TAG, "No change is needed for package: " + packageName + + ". Skipping suspending/un-suspending."); } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index ab9d1cfc2eec..73be5490e0e1 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2592,7 +2592,7 @@ public class UserManagerService extends IUserManager.Stub { } } if (scheduleWriteUser) { - scheduleWriteUser(userData); + scheduleWriteUser(userId); } } @@ -2902,7 +2902,7 @@ public class UserManagerService extends IUserManager.Stub { != newBaseRestrictions); if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) { - scheduleWriteUser(getUserDataNoChecks(userId)); + scheduleWriteUser(userId); } } @@ -2978,7 +2978,7 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mRestrictionsLock") private void applyUserRestrictionsLR(@UserIdInt int userId) { updateUserRestrictionsInternalLR(null, userId); - scheduleWriteUser(getUserDataNoChecks(userId)); + scheduleWriteUser(userId); } @GuardedBy("mRestrictionsLock") @@ -4129,14 +4129,14 @@ public class UserManagerService extends IUserManager.Stub { } } - private void scheduleWriteUser(UserData userData) { + private void scheduleWriteUser(@UserIdInt int userId) { if (DBG) { debug("scheduleWriteUser"); } // No need to wrap it within a lock -- worst case, we'll just post the same message // twice. - if (!mHandler.hasMessages(WRITE_USER_MSG, userData)) { - Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userData); + if (!mHandler.hasMessages(WRITE_USER_MSG, userId)) { + Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId); mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY); } } @@ -4152,7 +4152,7 @@ public class UserManagerService extends IUserManager.Stub { // Something went wrong, schedule full rewrite. UserData userData = getUserDataNoChecks(userId); if (userData != null) { - scheduleWriteUser(userData); + scheduleWriteUser(userId); } }); } @@ -6363,7 +6363,7 @@ public class UserManagerService extends IUserManager.Stub { userData.info.lastLoggedInTime = now; } userData.info.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT; - scheduleWriteUser(userData); + scheduleWriteUser(userId); } /** @@ -6533,7 +6533,7 @@ public class UserManagerService extends IUserManager.Stub { private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) { userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis(); - scheduleWriteUser(userData); + scheduleWriteUser(userData.info.id); } @Override @@ -6832,7 +6832,7 @@ public class UserManagerService extends IUserManager.Stub { case WRITE_USER_MSG: removeMessages(WRITE_USER_MSG, msg.obj); synchronized (mPackagesLock) { - int userId = ((UserData) msg.obj).info.id; + int userId = (int) msg.obj; UserData userData = getUserDataNoChecks(userId); if (userData != null) { writeUserLP(userData); diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index c9ebeaee88ce..5015985dd747 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -361,7 +361,8 @@ final class VerifyingSession { } final int verifierUserId = verifierUser.getIdentifier(); - List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages); + List<String> requiredVerifierPackages = new ArrayList<>( + Arrays.asList(mPm.mRequiredVerifierPackages)); boolean requiredVerifierPackagesOverridden = false; // Allow verifier override for ADB installations which could already be unverified using diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index b1b0c559aad4..4a03628ab8e5 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3569,6 +3569,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) { if (wallpaper == null) { pw.println(" (null entry)"); + return; } pw.print(" User "); pw.print(wallpaper.userId); pw.print(": id="); pw.print(wallpaper.wallpaperId); diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index 83804f75afea..32f7b9682fb7 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -466,8 +466,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, } boolean isAnimatingByRecents(@NonNull Task task) { - return task.isAnimatingByRecents() - || mService.mAtmService.getTransitionController().inRecentsTransition(task); + return task.isAnimatingByRecents(); } void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index fa3a186a6153..df360b86fdf8 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -44,6 +44,8 @@ import static com.android.server.accessibility.AccessibilityTraceProto.WHERE; import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.dumpSparseArray; +import static com.android.server.wm.WindowManagerService.dumpSparseArrayValues; import static com.android.server.wm.WindowTracing.WINSCOPE_EXT; import android.accessibilityservice.AccessibilityTrace; @@ -542,15 +544,12 @@ final class AccessibilityController { } void dump(PrintWriter pw, String prefix) { - for (int i = 0; i < mDisplayMagnifiers.size(); i++) { - final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i); - if (displayMagnifier != null) { - displayMagnifier.dump(pw, prefix - + "Magnification display# " + mDisplayMagnifiers.keyAt(i)); - } - } - pw.println(prefix - + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver); + dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display", + (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key), + dm -> dm.dump(pw, "")); + dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver, + "windows for accessibility observer"); + mAccessibilityWindowsPopulator.dump(pw, prefix); } void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) { diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index 21b241a0d117..afe164056ff4 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static com.android.server.wm.WindowManagerService.ValueDumper; +import static com.android.server.wm.WindowManagerService.dumpSparseArray; import static com.android.server.wm.utils.RegionUtils.forEachRect; import android.annotation.NonNull; @@ -39,7 +41,9 @@ import android.view.WindowManager; import android.window.WindowInfosListener; import com.android.internal.annotations.GuardedBy; +import com.android.server.wm.WindowManagerService.KeyDumper; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -562,6 +566,35 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { notifyWindowsChanged(displayIdsForWindowsChanged); } + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.println("AccessibilityWindowsPopulator"); + String prefix2 = prefix + " "; + + pw.print(prefix2); pw.print("mWindowsNotificationEnabled: "); + pw.println(mWindowsNotificationEnabled); + + if (mVisibleWindows.isEmpty()) { + pw.print(prefix2); pw.println("No visible windows"); + } else { + pw.print(prefix2); pw.print(mVisibleWindows.size()); + pw.print(" visible windows: "); pw.println(mVisibleWindows); + } + KeyDumper noKeyDumper = (i, k) -> {}; // display id is already shown on value; + KeyDumper displayDumper = (i, d) -> pw.printf("%sDisplay #%d: ", prefix, d); + // Ideally magnificationSpecDumper should use spec.dump(pw), but there is no such method + ValueDumper<MagnificationSpec> magnificationSpecDumper = spec -> pw.print(spec); + + dumpSparseArray(pw, prefix2, mDisplayInfos, "display info", noKeyDumper, d -> pw.print(d)); + dumpSparseArray(pw, prefix2, mInputWindowHandlesOnDisplays, "window handles on display", + displayDumper, list -> pw.print(list)); + dumpSparseArray(pw, prefix2, mMagnificationSpecInverseMatrix, "magnification spec matrix", + noKeyDumper, matrix -> matrix.dump(pw)); + dumpSparseArray(pw, prefix2, mCurrentMagnificationSpec, "current magnification spec", + noKeyDumper, magnificationSpecDumper); + dumpSparseArray(pw, prefix2, mPreviousMagnificationSpec, "previous magnification spec", + noKeyDumper, magnificationSpecDumper); + } + @GuardedBy("mLock") private void releaseResources() { mInputWindowHandlesOnDisplays.clear(); diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ff1c28ad1973..62144401223b 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -78,8 +78,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManagerInternal; -import android.content.pm.ParceledListSlice; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; @@ -1645,18 +1643,15 @@ class ActivityClientController extends IActivityClientController.Stub { launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME); } - // If the activity is one of the main entry points for the application, then we should + // If the activity was launched directly from the home screen, then we should // refrain from finishing the activity and instead move it to the back to keep it in // memory. The requirements for this are: // 1. The activity is the last running activity in the task. // 2. The current activity is the base activity for the task. - // 3. a. If the activity was launched by the home process, we trust that its intent - // was resolved, so we check if the it is a main intent for the application. - // b. Otherwise, we query Package Manager to verify whether the activity is a - // launcher activity for the application. + // 3. The activity was launched by the home process, and is one of the main entry + // points for the application. if (baseActivityIntent != null && isLastRunningActivity - && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) - || isLauncherActivity(baseActivityIntent.getComponent()))) { + && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) { moveActivityTaskToBack(token, true /* nonRoot */); return; } @@ -1668,31 +1663,6 @@ class ActivityClientController extends IActivityClientController.Stub { } } - /** - * Queries PackageManager to see if the given activity is one of the main entry point for the - * application. This should not be called with the WM lock held. - */ - @SuppressWarnings("unchecked") - private boolean isLauncherActivity(@NonNull ComponentName activity) { - final Intent queryIntent = new Intent(Intent.ACTION_MAIN); - queryIntent.addCategory(Intent.CATEGORY_LAUNCHER); - queryIntent.setPackage(activity.getPackageName()); - try { - final ParceledListSlice<ResolveInfo> resolved = - mService.getPackageManager().queryIntentActivities( - queryIntent, null, 0, mContext.getUserId()); - if (resolved == null) return false; - for (final ResolveInfo ri : resolved.getList()) { - if (ri.getComponentInfo().getComponentName().equals(activity)) { - return true; - } - } - } catch (RemoteException e) { - Slog.e(TAG, "Failed to query intent activities", e); - } - return false; - } - @Override public void enableTaskLocaleOverride(IBinder token) { if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 8346e7c83190..1bcc05e6f116 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3522,7 +3522,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean endTask = task.getTopNonFinishingActivity() == null && !task.isClearingToReuseTask(); - mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this); + final Transition newTransition = + mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this); if (isState(RESUMED)) { if (endTask) { mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted( @@ -3543,8 +3544,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // the best capture timing (e.g. IME window capture), // No need additional task capture while task is controlled by RecentsAnimation. if (mAtmService.mWindowManager.mTaskSnapshotController != null - && !(task.isAnimatingByRecents() - || mTransitionController.inRecentsTransition(task))) { + && !task.isAnimatingByRecents()) { final ArraySet<Task> tasks = Sets.newArraySet(task); mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks); mAtmService.mWindowManager.mTaskSnapshotController @@ -3576,7 +3576,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (!isState(PAUSING)) { if (mVisibleRequested) { // Prepare and execute close transition. - prepareActivityHideTransitionAnimation(); + if (mTransitionController.isShellTransitionsEnabled()) { + setVisibility(false); + if (newTransition != null) { + // This is a transition specifically for this close operation, so set + // ready now. + newTransition.setReady(mDisplayContent, true); + } + } else { + prepareActivityHideTransitionAnimation(); + } } final boolean removedActivity = completeFinishing("finishIfPossible") == null; @@ -7917,6 +7926,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( task.mTaskId, requestedOrientation); + + mDisplayContent.getDisplayRotation().onSetRequestedOrientation(); } /* @@ -9760,6 +9771,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * directly with keeping its record. */ void restartProcessIfVisible() { + if (finishing) return; Slog.i(TAG, "Request to restart process of " + this); // Reset the existing override configuration so it can be updated according to the latest @@ -10522,11 +10534,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean isSyncFinished() { - if (task != null && mTransitionController.isTransientHide(task)) { - // The activity keeps visibleRequested but may be hidden later, so no need to wait for - // it to be drawn. - return true; - } if (!super.isSyncFinished()) return false; if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController .isVisibilityUnknown(this)) { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 7c1e9071b926..bfe298653584 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -577,7 +577,6 @@ public class ActivityStartController { final Transition transition = controller.getCollectingTransition(); if (transition != null) { transition.setRemoteAnimationApp(r.app.getThread()); - controller.collect(task); controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask)); } task.moveToFront("startExistingRecents"); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d4f151f5c66d..c5e75faf2c6c 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1606,6 +1606,8 @@ class ActivityStarter { transitionController.requestStartTransition(newTransition, mTargetTask == null ? started.getTask() : mTargetTask, remoteTransition, null /* displayChange */); + } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) { + // Do nothing if the activity is started and is resumed directly. } else if (isStarted) { // Make the collecting transition wait until this request is ready. transitionController.setReady(started, false); @@ -2546,6 +2548,7 @@ class ActivityStarter { mAvoidMoveToFront = false; mFrozeTaskList = false; mTransientLaunch = false; + mPriorAboveTask = null; mDisplayLockAndOccluded = false; mVoiceSession = null; diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 48cf567ba9be..d916a1be1a03 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -23,6 +23,7 @@ import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Handler; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; @@ -172,7 +173,7 @@ class BLASTSyncEngine { if (ran) { return; } - mWm.mH.removeCallbacks(this); + mHandler.removeCallbacks(this); ran = true; SurfaceControl.Transaction t = new SurfaceControl.Transaction(); for (WindowContainer wc : wcAwaitingCommit) { @@ -199,13 +200,13 @@ class BLASTSyncEngine { }; CommitCallback callback = new CommitCallback(); merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted); - mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION); + mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); mListener.onTransactionReady(mSyncId, merged); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); mActiveSyncs.remove(mSyncId); - mWm.mH.removeCallbacks(mOnTimeout); + mHandler.removeCallbacks(mOnTimeout); // Immediately start the next pending sync-transaction if there is one. if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) { @@ -216,7 +217,7 @@ class BLASTSyncEngine { throw new IllegalStateException("Pending Sync Set didn't start a sync."); } // Post this so that the now-playing transition setup isn't interrupted. - mWm.mH.post(() -> { + mHandler.post(() -> { synchronized (mWm.mGlobalLock) { pt.mApplySync.run(); } @@ -228,7 +229,7 @@ class BLASTSyncEngine { if (mReady == ready) { return; } - ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId); + ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready); mReady = ready; if (!ready) return; mWm.mWindowPlacerLocked.requestTraversal(); @@ -269,6 +270,7 @@ class BLASTSyncEngine { } private final WindowManagerService mWm; + private final Handler mHandler; private int mNextSyncId = 0; private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>(); @@ -280,7 +282,13 @@ class BLASTSyncEngine { private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>(); BLASTSyncEngine(WindowManagerService wms) { + this(wms, wms.mH); + } + + @VisibleForTesting + BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) { mWm = wms; + mHandler = mainHandler; } /** @@ -305,8 +313,8 @@ class BLASTSyncEngine { if (mActiveSyncs.size() != 0) { // We currently only support one sync at a time, so start a new SyncGroup when there is // another may cause issue. - ProtoLog.w(WM_DEBUG_SYNC_ENGINE, - "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId); + Slog.e(TAG, "SyncGroup " + s.mSyncId + + ": Started when there is other active SyncGroup"); } mActiveSyncs.put(s.mSyncId, s); ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s", @@ -325,7 +333,7 @@ class BLASTSyncEngine { @VisibleForTesting void scheduleTimeout(SyncGroup s, long timeoutMs) { - mWm.mH.postDelayed(s.mOnTimeout, timeoutMs); + mHandler.postDelayed(s.mOnTimeout, timeoutMs); } void addToSyncSet(int id, WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 587e7204f993..7b562b0bc964 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -286,8 +286,8 @@ class BackNavigationController { currentActivity.getCustomAnimation(false/* open */); if (customAppTransition != null) { infoBuilder.setCustomAnimation(currentActivity.packageName, - customAppTransition.mExitAnim, customAppTransition.mEnterAnim, + customAppTransition.mExitAnim, customAppTransition.mBackgroundColor); } } @@ -585,14 +585,14 @@ class BackNavigationController { * The closing target should only exist in close list, but the opening target can be either in * open or close list. */ - void onTransactionReady(Transition transition) { + void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) { if (!isMonitoringTransition()) { return; } - final ArraySet<WindowContainer> targets = transition.mParticipants; for (int i = targets.size() - 1; i >= 0; --i) { - final WindowContainer wc = targets.valueAt(i); - if (wc.asActivityRecord() == null && wc.asTask() == null) { + final WindowContainer wc = targets.get(i).mContainer; + if (wc.asActivityRecord() == null && wc.asTask() == null + && wc.asTaskFragment() == null) { continue; } // WC can be visible due to setLaunchBehind @@ -605,6 +605,9 @@ class BackNavigationController { final boolean matchAnimationTargets = isWaitBackTransition() && (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK) && mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps); + ProtoLog.d(WM_DEBUG_BACK_PREVIEW, + "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b", + mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets); if (!matchAnimationTargets) { mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps); } else { @@ -829,10 +832,16 @@ class BackNavigationController { if (!mComposed) { return false; } + + // WC must be ActivityRecord in legacy transition, but it also can be Task or + // TaskFragment when using Shell transition. + // Open target: Can be Task or ActivityRecord or TaskFragment + // Close target: Limit to the top activity for now, to reduce the chance of misjudgment. final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget; if (mSwitchType == TASK_SWITCH) { return wc == target - || (wc.asTask() != null && wc.hasChild(target)); + || (wc.asTask() != null && wc.hasChild(target)) + || (wc.asActivityRecord() != null && target.hasChild(wc)); } else if (mSwitchType == ACTIVITY_SWITCH) { return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target)); } 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 00299c286b08..26f56a2e5c0b 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -767,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); @@ -780,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. @@ -792,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 bec58b848478..c2bc4591ce0d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -775,6 +775,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ DisplayWindowPolicyControllerHelper mDwpcHelper; + private final DisplayRotationReversionController mRotationReversionController; + private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> { WindowStateAnimator winAnimator = w.mWinAnimator; final ActivityRecord activity = w.mActivityRecord; @@ -1204,6 +1206,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ false) ? new DisplayRotationCompatPolicy(this) : null; + mRotationReversionController = new DisplayRotationReversionController(this); mInputMonitor = new InputMonitor(mWmService, this); mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this); @@ -1333,6 +1336,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp .show(mA11yOverlayLayer); } + DisplayRotationReversionController getRotationReversionController() { + return mRotationReversionController; + } + boolean isReady() { // The display is ready when the system and the individual display are both ready. return mWmService.mDisplayReady && mDisplayReady; @@ -1711,9 +1718,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private boolean updateOrientation(boolean forceUpdate) { + final WindowContainer prevOrientationSource = mLastOrientationSource; final int orientation = getOrientation(); // The last orientation source is valid only after getOrientation. final WindowContainer orientationSource = getLastOrientationSource(); + if (orientationSource != prevOrientationSource + && mRotationReversionController.isRotationReversionEnabled()) { + mRotationReversionController.updateForNoSensorOverride(); + } final ActivityRecord r = orientationSource != null ? orientationSource.asActivityRecord() : null; if (r != null) { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 628f4d3a85d6..20048ce543f3 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -33,6 +33,9 @@ import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATI import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION; import static com.android.server.wm.DisplayRotationProto.ROTATION; import static com.android.server.wm.DisplayRotationProto.USER_ROTATION; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_HALF_FOLD; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; @@ -97,6 +100,8 @@ public class DisplayRotation { // config changes and unexpected jumps while folding the device to closed state. private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800; + private static final int ROTATION_UNDEFINED = -1; + private static class RotationAnimationPair { @AnimRes int mEnter; @@ -104,6 +109,9 @@ public class DisplayRotation { int mExit; } + @Nullable + final FoldController mFoldController; + private final WindowManagerService mService; private final DisplayContent mDisplayContent; private final DisplayPolicy mDisplayPolicy; @@ -125,8 +133,6 @@ public class DisplayRotation { private OrientationListener mOrientationListener; private StatusBarManagerInternal mStatusBarManagerInternal; private SettingsObserver mSettingsObserver; - @Nullable - private FoldController mFoldController; @NonNull private final DeviceStateController mDeviceStateController; @NonNull @@ -189,6 +195,12 @@ public class DisplayRotation { */ private int mShowRotationSuggestions; + /** + * The most recent {@link Surface.Rotation} choice shown to the user for confirmation, or + * {@link #ROTATION_UNDEFINED} + */ + private int mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED; + private static final int ALLOW_ALL_ROTATIONS_UNDEFINED = -1; private static final int ALLOW_ALL_ROTATIONS_DISABLED = 0; private static final int ALLOW_ALL_ROTATIONS_ENABLED = 1; @@ -291,7 +303,11 @@ public class DisplayRotation { if (mSupportAutoRotation && mContext.getResources().getBoolean( R.bool.config_windowManagerHalfFoldAutoRotateOverride)) { mFoldController = new FoldController(); + } else { + mFoldController = null; } + } else { + mFoldController = null; } } @@ -349,6 +365,11 @@ public class DisplayRotation { return -1; } + @VisibleForTesting + boolean useDefaultSettingsProvider() { + return isDefaultDisplay; + } + /** * Updates the configuration which may have different values depending on current user, e.g. * runtime resource overlay. @@ -894,7 +915,8 @@ public class DisplayRotation { @VisibleForTesting void setUserRotation(int userRotationMode, int userRotation) { - if (isDefaultDisplay) { + mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED; + if (useDefaultSettingsProvider()) { // We'll be notified via settings listener, so we don't need to update internal values. final ContentResolver res = mContext.getContentResolver(); final int accelerometerRotation = @@ -1613,6 +1635,17 @@ public class DisplayRotation { } } + /** + * Called from {@link ActivityRecord#setRequestedOrientation(int)} + */ + void onSetRequestedOrientation() { + if (mCompatPolicyForImmersiveApps == null + || mRotationChoiceShownToUserForConfirmation == ROTATION_UNDEFINED) { + return; + } + mOrientationListener.onProposedRotationChanged(mRotationChoiceShownToUserForConfirmation); + } + void dump(String prefix, PrintWriter pw) { pw.println(prefix + "DisplayRotation"); pw.println(prefix + " mCurrentAppOrientation=" @@ -1839,7 +1872,7 @@ public class DisplayRotation { return false; } if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) { - return !(isTabletop ^ mTabletopRotations.contains(mRotation)); + return isTabletop == mTabletopRotations.contains(mRotation); } return true; } @@ -1863,14 +1896,17 @@ public class DisplayRotation { return mDeviceState == DeviceStateController.DeviceState.OPEN && !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving && mInHalfFoldTransition - && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted. + && mDisplayContent.getRotationReversionController().isOverrideActive( + REVERSION_TYPE_HALF_FOLD) && mUserRotationMode - == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked. + == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked. } int revertOverriddenRotation() { int savedRotation = mHalfFoldSavedRotation; mHalfFoldSavedRotation = -1; + mDisplayContent.getRotationReversionController() + .revertOverride(REVERSION_TYPE_HALF_FOLD); mInHalfFoldTransition = false; return savedRotation; } @@ -1890,6 +1926,8 @@ public class DisplayRotation { && mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) { // The device has transitioned to HALF_FOLDED state: save the current rotation and // update the device rotation. + mDisplayContent.getRotationReversionController().beforeOverrideApplied( + REVERSION_TYPE_HALF_FOLD); mHalfFoldSavedRotation = mRotation; mDeviceState = newState; // Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will @@ -2012,9 +2050,11 @@ public class DisplayRotation { mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0); dispatchProposedRotation(rotation); if (isRotationChoiceAllowed(rotation)) { + mRotationChoiceShownToUserForConfirmation = rotation; final boolean isValid = isValidRotationChoice(rotation); sendProposedRotationChangeToStatusBarInternal(rotation, isValid); } else { + mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED; mService.updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */); } @@ -2093,6 +2133,8 @@ public class DisplayRotation { final int mHalfFoldSavedRotation; final boolean mInHalfFoldTransition; final DeviceStateController.DeviceState mDeviceState; + @Nullable final boolean[] mRotationReversionSlots; + @Nullable final String mDisplayRotationCompatPolicySummary; Record(DisplayRotation dr, int fromRotation, int toRotation) { @@ -2133,6 +2175,8 @@ public class DisplayRotation { ? null : dc.mDisplayRotationCompatPolicy .getSummaryForDisplayRotationHistoryRecord(); + mRotationReversionSlots = + dr.mDisplayContent.getRotationReversionController().getSlotsCopy(); } void dump(String prefix, PrintWriter pw) { @@ -2158,6 +2202,12 @@ public class DisplayRotation { if (mDisplayRotationCompatPolicySummary != null) { pw.println(prefix + mDisplayRotationCompatPolicySummary); } + if (mRotationReversionSlots != null) { + pw.println(prefix + " reversionSlots= NOSENSOR " + + mRotationReversionSlots[REVERSION_TYPE_NOSENSOR] + ", CAMERA " + + mRotationReversionSlots[REVERSION_TYPE_CAMERA_COMPAT] + " HALF_FOLD " + + mRotationReversionSlots[REVERSION_TYPE_HALF_FOLD]); + } } } diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index fb72d6c6b56d..ae93a9496f7c 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -33,6 +33,7 @@ import static android.view.Display.TYPE_INTERNAL; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; +import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; import android.annotation.NonNull; import android.annotation.Nullable; @@ -156,6 +157,11 @@ final class DisplayRotationCompatPolicy { @ScreenOrientation int getOrientation() { mLastReportedOrientation = getOrientationInternal(); + if (mLastReportedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + rememberOverriddenOrientationIfNeeded(); + } else { + restoreOverriddenOrientationIfNeeded(); + } return mLastReportedOrientation; } @@ -277,6 +283,34 @@ final class DisplayRotationCompatPolicy { + " }"; } + private void restoreOverriddenOrientationIfNeeded() { + if (!isOrientationOverridden()) { + return; + } + if (mDisplayContent.getRotationReversionController().revertOverride( + REVERSION_TYPE_CAMERA_COMPAT)) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Reverting orientation after camera compat force rotation"); + // Reset last orientation source since we have reverted the orientation. + mDisplayContent.mLastOrientationSource = null; + } + } + + private boolean isOrientationOverridden() { + return mDisplayContent.getRotationReversionController().isOverrideActive( + REVERSION_TYPE_CAMERA_COMPAT); + } + + private void rememberOverriddenOrientationIfNeeded() { + if (!isOrientationOverridden()) { + mDisplayContent.getRotationReversionController().beforeOverrideApplied( + REVERSION_TYPE_CAMERA_COMPAT); + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Saving original orientation before camera compat, last orientation is %d", + mDisplayContent.getLastOrientation()); + } + } + // Refreshing only when configuration changes after rotation. private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig, Configuration lastReportedConfig) { diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java new file mode 100644 index 000000000000..d3a8a82f8f87 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; + +import android.annotation.Nullable; +import android.app.WindowConfiguration; +import android.content.ActivityInfoProto; +import android.view.Surface; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.policy.WindowManagerPolicy; + +/** + * Defines the behavior of reversion from device rotation overrides. + * + * <p>There are 3 override types: + * <ol> + * <li>The top application has {@link SCREEN_ORIENTATION_NOSENSOR} set and is rotated to + * {@link ROTATION_0}. + * <li>Camera compat treatment has rotated the app {@link DisplayRotationCompatPolicy}. + * <li>The device is half-folded and has auto-rotate is temporarily enabled. + * </ol> + * + * <p>Before an override is enabled, a component should call {@code beforeOverrideApplied}. When + * it wishes to revert, it should call {@code revertOverride}. The user rotation will be restored + * if there are no other overrides present. + */ +final class DisplayRotationReversionController { + + static final int REVERSION_TYPE_NOSENSOR = 0; + static final int REVERSION_TYPE_CAMERA_COMPAT = 1; + static final int REVERSION_TYPE_HALF_FOLD = 2; + private static final int NUM_SLOTS = 3; + + @Surface.Rotation + private int mUserRotationOverridden = WindowConfiguration.ROTATION_UNDEFINED; + @WindowManagerPolicy.UserRotationMode + private int mUserRotationModeOverridden; + + private final boolean[] mSlots = new boolean[NUM_SLOTS]; + private final DisplayContent mDisplayContent; + + DisplayRotationReversionController(DisplayContent content) { + mDisplayContent = content; + } + + boolean isRotationReversionEnabled() { + return mDisplayContent.mDisplayRotationCompatPolicy != null + || mDisplayContent.getDisplayRotation().mFoldController != null + || mDisplayContent.getIgnoreOrientationRequest(); + } + + void beforeOverrideApplied(int slotIndex) { + if (mSlots[slotIndex]) return; + maybeSaveUserRotation(); + mSlots[slotIndex] = true; + } + + boolean isOverrideActive(int slotIndex) { + return mSlots[slotIndex]; + } + + @Nullable + boolean[] getSlotsCopy() { + return isRotationReversionEnabled() ? mSlots.clone() : null; + } + + void updateForNoSensorOverride() { + if (!mSlots[REVERSION_TYPE_NOSENSOR]) { + if (isTopFullscreenActivityNoSensor()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override detected"); + beforeOverrideApplied(REVERSION_TYPE_NOSENSOR); + } + } else { + if (!isTopFullscreenActivityNoSensor()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override is absent: reverting"); + revertOverride(REVERSION_TYPE_NOSENSOR); + } + } + } + + boolean isAnyOverrideActive() { + for (int i = 0; i < NUM_SLOTS; ++i) { + if (mSlots[i]) { + return true; + } + } + return false; + } + + boolean revertOverride(int slotIndex) { + if (!mSlots[slotIndex]) return false; + mSlots[slotIndex] = false; + if (isAnyOverrideActive()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Other orientation overrides are in place: not reverting"); + return false; + } + // Only override if the rotation is frozen and there are no other active slots. + if (mDisplayContent.getDisplayRotation().isRotationFrozen()) { + mDisplayContent.getDisplayRotation().setUserRotation( + mUserRotationModeOverridden, + mUserRotationOverridden); + return true; + } else { + return false; + } + } + + private void maybeSaveUserRotation() { + if (!isAnyOverrideActive()) { + mUserRotationModeOverridden = + mDisplayContent.getDisplayRotation().getUserRotationMode(); + mUserRotationOverridden = mDisplayContent.getDisplayRotation().getUserRotation(); + } + } + + private boolean isTopFullscreenActivityNoSensor() { + final Task topFullscreenTask = + mDisplayContent.getTask( + t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN); + if (topFullscreenTask != null) { + final ActivityRecord topActivity = + topFullscreenTask.topRunningActivity(); + return topActivity != null && topActivity.getOrientation() + == ActivityInfoProto.SCREEN_ORIENTATION_NOSENSOR; + } + return false; + } +} 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 93233dd4bda8..ff1deaf415b3 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1597,11 +1597,10 @@ final class LetterboxUiController { inheritConfiguration(firstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, firstOpaqueActivityBeneath, - (opaqueConfig, transparentConfig) -> { - final Configuration mutatedConfiguration = - fromOriginalTranslucentConfig(transparentConfig); + (opaqueConfig, transparentOverrideConfig) -> { + resetTranslucentOverrideConfig(transparentOverrideConfig); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); - final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); + final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. @@ -1610,9 +1609,9 @@ final class LetterboxUiController { parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. - mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); + transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect()); inheritConfiguration(firstOpaqueActivityBeneath); - return mutatedConfiguration; + return transparentOverrideConfig; }); } @@ -1691,20 +1690,16 @@ final class LetterboxUiController { true /* traverseTopToBottom */)); } - // When overriding translucent activities configuration we need to keep some of the - // original properties - private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) { - final Configuration configuration = new Configuration(translucentConfig); + /** Resets the screen size related fields so they can be resolved by requested bounds later. */ + private static void resetTranslucentOverrideConfig(Configuration config) { // The values for the following properties will be defined during the configuration // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the // properties inherited from the first not finishing opaque activity beneath. - configuration.orientation = ORIENTATION_UNDEFINED; - configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; - configuration.screenHeightDp = - configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; - configuration.smallestScreenWidthDp = - configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; - return configuration; + config.orientation = ORIENTATION_UNDEFINED; + config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; + config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; + config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp = + SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; } private void inheritConfiguration(ActivityRecord firstOpaque) { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index b38666522754..f5079d37b324 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); } } @@ -1511,7 +1512,7 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { - if (removedTask.hasChild()) { + if (removedTask.hasChild() && !removedTask.isActivityTypeHome()) { Slog.i(TAG, "Add " + removedTask + " to hidden list because adding " + task); // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index db4453297001..fb592e124920 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; @@ -3008,7 +3006,8 @@ class Task extends TaskFragment { /** Checking if self or its child tasks are animated by recents animation. */ boolean isAnimatingByRecents() { - return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS); + return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS) + || mTransitionController.isTransientHide(this); } WindowState getTopVisibleAppMainWindow() { @@ -3254,22 +3253,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(); } @@ -4687,7 +4688,7 @@ class Task extends TaskFragment { if (!isAttached()) { return; } - mTransitionController.collect(this); + mTransitionController.recordTaskOrder(this); final TaskDisplayArea taskDisplayArea = getDisplayArea(); 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 1fb353476592..3a909cedc5ab 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -109,7 +109,7 @@ import java.util.function.Predicate; */ class Transition implements BLASTSyncEngine.TransactionReadyListener { private static final String TAG = "Transition"; - private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition"; + private static final String TRACE_NAME_PLAY_TRANSITION = "playing"; /** The default package for resources */ private static final String DEFAULT_PACKAGE = "android"; @@ -511,8 +511,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (mParticipants.contains(wc)) return; // Wallpaper is like in a static drawn state unless display may have changes, so exclude // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. - final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent); - if (needSyncDraw) { + final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent)) + // Transient-hide may be hidden later, so no need to request redraw. + && !isInTransientHide(wc); + if (needSync) { mSyncEngine.addToSyncSet(mSyncId, wc); } ChangeInfo info = mChanges.get(wc); @@ -521,10 +523,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mChanges.put(wc, info); } mParticipants.add(wc); - if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) { - mTargetDisplays.add(wc.getDisplayContent()); - addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart); - } + recordDisplay(wc.getDisplayContent()); if (info.mShowWallpaper) { // Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set. final WindowState wallpaper = @@ -535,6 +534,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } + private void recordDisplay(DisplayContent dc) { + if (dc == null || mTargetDisplays.contains(dc)) return; + mTargetDisplays.add(dc); + addOnTopTasks(dc, mOnTopTasksStart); + } + + /** + * Records information about the initial task order. This does NOT collect anything. Call this + * before any ordering changes *could* occur, but it is not known yet if it will occur. + */ + void recordTaskOrder(WindowContainer from) { + recordDisplay(from.getDisplayContent()); + } + /** Adds the top non-alwaysOnTop tasks within `task` to `out`. */ private static void addOnTopTasks(Task task, ArrayList<Task> out) { for (int i = task.getChildCount() - 1; i >= 0; --i) { @@ -870,8 +883,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ void finishTransition() { if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) { - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, - System.identityHashCode(this)); + asyncTraceEnd(System.identityHashCode(this)); } mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos(); mController.mLoggerHandler.post(mLogger::logOnFinish); @@ -907,6 +919,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> participant = mParticipants.valueAt(i); final ActivityRecord ar = participant.asActivityRecord(); if (ar != null) { + final Task task = ar.getTask(); + if (task == null) continue; boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); // We need both the expected visibility AND current requested-visibility to be // false. If it is expected-visible but not currently visible, it means that @@ -925,9 +939,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (commitVisibility) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Commit activity becoming invisible: %s", ar); - final Task task = ar.getTask(); - if (task != null && !task.isVisibleRequested() - && mTransientLaunches != null) { + if (mTransientLaunches != null && !task.isVisibleRequested()) { // If transition is transient, then snapshots are taken at end of // transition. mController.mSnapshotController.mTaskSnapshotController @@ -952,8 +964,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Since transient launches don't automatically take focus, make sure we // synchronize focus since we committed to the launch. - if (ar.isTopRunningActivity()) { - ar.moveFocusableActivityToTop("transitionFinished"); + if (!task.isFocused() && ar.isTopRunningActivity()) { + mController.mAtm.setLastResumedActivityUncheckLocked(ar, + "transitionFinished"); } } continue; @@ -1199,13 +1212,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (primaryDisplay.isKeyguardLocked()) { mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; } - // Check whether the participants were animated from back navigation. - mController.mAtm.mBackNavigationController.onTransactionReady(this); - collectOrderChanges(); // Resolve the animating targets from the participants. mTargets = calculateTargets(mParticipants, mChanges); + // Check whether the participants were animated from back navigation. + mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction); info.setDebugId(mSyncId); @@ -1322,8 +1334,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.getTransitionPlayer().onTransitionReady( mToken, info, transaction, mFinishTransaction); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION, - System.identityHashCode(this)); + asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this)); } } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the @@ -2310,6 +2321,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return isCollecting() && mSyncId >= 0; } + static void asyncTraceBegin(@NonNull String name, int cookie) { + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie); + } + + static void asyncTraceEnd(int cookie) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie); + } + @VisibleForTesting static class ChangeInfo { private static final int FLAG_NONE = 0; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index bcb8c46de5ed..3bf896939f49 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -37,7 +37,6 @@ import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; -import android.os.Trace; import android.util.ArrayMap; import android.util.Slog; import android.util.TimeUtils; @@ -334,28 +333,6 @@ class TransitionController { return inCollectingTransition(wc) || inPlayingTransition(wc); } - boolean inRecentsTransition(@NonNull WindowContainer wc) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - // TODO(b/221417431): replace this with deterministic snapshots - if (mCollectingTransition == null) break; - if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 - && mCollectingTransition.mParticipants.contains(wc)) { - return true; - } - } - - for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { - for (WindowContainer p = wc; p != null; p = p.getParent()) { - // TODO(b/221417431): replace this with deterministic snapshots - if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0 - && mPlayingTransitions.get(i).mParticipants.contains(p)) { - return true; - } - } - } - return false; - } - /** @return {@code true} if wc is in a participant subtree */ boolean isTransitionOnDisplay(@NonNull DisplayContent dc) { if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) { @@ -577,12 +554,16 @@ class TransitionController { return transition; } - /** Requests transition for a window container which will be removed or invisible. */ - void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { - if (mTransitionPlayer == null) return; + /** + * Requests transition for a window container which will be removed or invisible. + * @return the new transition if it was created for this request, `null` otherwise. + */ + Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) { + if (mTransitionPlayer == null) return null; + Transition out = null; if (wc.isVisibleRequested()) { if (!isCollecting()) { - requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), + out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */), wc.asTask(), null /* remoteTransition */, null /* displayChange */); } collectExistenceChange(wc); @@ -591,6 +572,7 @@ class TransitionController { // collecting, this should be a member just in case. collect(wc); } + return out; } /** @see Transition#collect */ @@ -605,6 +587,12 @@ class TransitionController { mCollectingTransition.collectExistenceChange(wc); } + /** @see Transition#recordTaskOrder */ + void recordTaskOrder(@NonNull WindowContainer wc) { + if (mCollectingTransition == null) return; + mCollectingTransition.recordTaskOrder(wc); + } + /** * Collects the window containers which need to be synced with the changing display area into * the current collecting transition. @@ -762,12 +750,12 @@ class TransitionController { // happening in app), so pause task snapshot persisting to not increase the load. mAtm.mWindowManager.mSnapshotController.setPause(true); mAnimatingState = true; - Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); + Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */); } else if (!animatingState && mAnimatingState) { t.setEarlyWakeupEnd(); mAtm.mWindowManager.mSnapshotController.setPause(false); mAnimatingState = false; - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0); + Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */); } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 41176410a789..cf6efd28acb7 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -4047,7 +4047,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final Configuration mergedConfiguration = configurationMerger != null ? configurationMerger.merge(mergedOverrideConfig, - receiver.getConfiguration()) + receiver.getRequestedOverrideConfiguration()) : supplier.getConfiguration(); receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index cd4d6e4f1600..82c057b99c10 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -235,6 +235,7 @@ import android.util.EventLog; import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; @@ -6697,9 +6698,8 @@ public class WindowManagerService extends IWindowManager.Stub mInputManagerCallback.dump(pw, " "); mSnapshotController.dump(pw, " "); - if (mAccessibilityController.hasCallbacks()) { - mAccessibilityController.dump(pw, " "); - } + + dumpAccessibilityController(pw, /* force= */ false); if (dumpAll) { final WindowState imeWindow = mRoot.getCurrentInputMethodWindow(); @@ -6736,6 +6736,23 @@ public class WindowManagerService extends IWindowManager.Stub } } + private void dumpAccessibilityController(PrintWriter pw, boolean force) { + boolean hasCallbacks = mAccessibilityController.hasCallbacks(); + if (!hasCallbacks && !force) { + return; + } + if (!hasCallbacks) { + pw.println("AccessibilityController doesn't have callbacks, but printing it anways:"); + } else { + pw.println("AccessibilityController:"); + } + mAccessibilityController.dump(pw, " "); + } + + private void dumpAccessibilityLocked(PrintWriter pw) { + dumpAccessibilityController(pw, /* force= */ true); + } + private boolean dumpWindows(PrintWriter pw, String name, boolean dumpAll) { final ArrayList<WindowState> windows = new ArrayList(); if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) { @@ -6855,6 +6872,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(" d[isplays]: active display contents"); pw.println(" t[okens]: token list"); pw.println(" w[indows]: window list"); + pw.println(" a11y[accessibility]: accessibility-related state"); pw.println(" package-config: installed packages having app-specific config"); pw.println(" trace: print trace status and write Winscope trace to file"); pw.println(" cmd may also be a NAME to dump windows. NAME may"); @@ -6918,6 +6936,11 @@ public class WindowManagerService extends IWindowManager.Stub dumpWindowsLocked(pw, true, null); } return; + } else if ("accessibility".equals(cmd) || "a11y".equals(cmd)) { + synchronized (mGlobalLock) { + dumpAccessibilityLocked(pw); + } + return; } else if ("all".equals(cmd)) { synchronized (mGlobalLock) { dumpWindowsLocked(pw, true, null); @@ -9437,4 +9460,53 @@ public class WindowManagerService extends IWindowManager.Stub return List.copyOf(notifiedApps); } } + + // TODO(b/271188189): move dump stuff below to common code / add unit tests + + interface ValueDumper<T> { + void dump(T value); + } + + interface KeyDumper{ + void dump(int index, int key); + } + + static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name) { + dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valuedumper= */ null); + } + + static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array, + String name) { + dumpSparseArray(pw, prefix, array, name, (i, k) -> {}, /* valueDumper= */ null); + } + + static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, + String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) { + int size = array.size(); + if (size == 0) { + pw.print(prefix); pw.print("No "); pw.print(name); pw.println("s"); + return; + } + pw.print(prefix); pw.print(size); pw.print(' '); + pw.print(name); pw.print(size > 1 ? "s" : ""); pw.println(':'); + + String prefix2 = prefix + " "; + for (int i = 0; i < size; i++) { + int key = array.keyAt(i); + T value = array.valueAt(i); + if (keyDumper != null) { + keyDumper.dump(i, key); + } else { + pw.print(prefix2); pw.print(i); pw.print(": "); pw.print(key); pw.print("->"); + } + if (value == null) { + pw.print("(null)"); + } else if (valueDumper != null) { + valueDumper.dump(value); + } else { + pw.print(value); + } + pw.println(); + } + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 736f489cf99d..e5a49c3a0ee2 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(); } @@ -5646,7 +5647,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean isSyncFinished() { - if (!isVisibleRequested()) { + if (!isVisibleRequested() || isFullyTransparent()) { // Don't wait for invisible windows. However, we don't alter the state in case the // window becomes visible while the sync group is still active. return true; diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java index bbebbf2383f5..19a0c5e8adcb 100644 --- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java @@ -27,9 +27,10 @@ import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; +import java.util.Set; /** * Central session for a single clearCredentialState request. This class listens to the @@ -43,11 +44,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta 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, sessionCallback, lock, userId, callingUid, request, callback, RequestInfo.TYPE_UNDEFINED, - callingAppInfo, cancellationSignal, startedTimestamp); + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); } /** @@ -65,7 +67,8 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerClearSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In startProviderSession - provider session created " + + "and being added for: " + providerInfo.getComponentName()); mProviders.put(providerClearSession.getComponentName().flattenToString(), providerClearSession); } @@ -75,12 +78,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta @Override // from provider session public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onStatusChanged with status: " + status); + Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); if (ProviderSession.isTerminatingStatus(status)) { - Log.i(TAG, "in onStatusChanged terminating status"); + Slog.d(TAG, "in onProviderStatusChanged terminating status"); onProviderTerminated(componentName); } else if (ProviderSession.isCompletionStatus(status)) { - Log.i(TAG, "in onStatusChanged isCompletionStatus status"); + Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status"); onProviderResponseComplete(componentName); } } diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 4c456a88b00b..a04143afadcd 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -33,11 +33,12 @@ import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; -import android.util.Log; +import android.util.Slog; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; +import java.util.Set; /** * Central session for a single {@link CredentialManager#createCredential} request. @@ -54,11 +55,12 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR CreateCredentialRequest request, ICreateCredentialCallback callback, CallingAppInfo callingAppInfo, + Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long startedTimestamp) { super(context, sessionCallback, lock, userId, callingUid, request, callback, RequestInfo.TYPE_CREATE, - callingAppInfo, cancellationSignal, startedTimestamp); + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); } /** @@ -75,7 +77,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerCreateSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In initiateProviderSession - provider session created and " + + "being added for: " + providerInfo.getComponentName()); mProviders.put(providerCreateSession.getComponentName().flattenToString(), providerCreateSession); } @@ -118,7 +121,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable CreateCredentialResponse response) { - Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); + Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get( componentName.flattenToString()).mProviderSessionMetric @@ -161,13 +164,13 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onProviderStatusChanged with status: " + status); + Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); // If all provider responses have been received, we can either need the UI, // or we need to respond with error. The only other case is the entry being // selected after the UI has been invoked which has a separate code path. if (!isAnyProviderPending()) { if (isUiInvocationNeeded()) { - Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); + Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 90f30b567023..06b96eb46ac1 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -458,6 +458,7 @@ public final class CredentialManagerService callback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); @@ -512,6 +513,7 @@ public final class CredentialManagerService getCredentialCallback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan, prepareGetCredentialCallback); @@ -629,6 +631,7 @@ public final class CredentialManagerService request, callback, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); @@ -793,7 +796,7 @@ public final class CredentialManagerService return DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, - false); + true); } finally { Binder.restoreCallingIdentity(origId); } @@ -846,6 +849,7 @@ public final class CredentialManagerService callback, request, constructCallingAppInfo(callingPackage, userId, null), + getEnabledProviders(), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index e16d48e0a680..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; @@ -38,34 +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; private UiStatus mStatus; - /** 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); - } + private final Set<ComponentName> mEnabledProviders; enum UiStatus { IN_PROGRESS, USER_INTERACTION, NOT_STARTED, TERMINATED } - @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver( + + @NonNull + private final ResultReceiver mResultReceiver = new ResultReceiver( new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { @@ -105,24 +102,33 @@ public class CredentialManagerUi { } } + /** 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; } @@ -139,37 +145,28 @@ public class CredentialManagerUi { /** * 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 2548bd888b9d..aeb4801628f2 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -30,11 +30,12 @@ import android.credentials.ui.RequestInfo; import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; -import android.util.Log; +import android.util.Slog; import com.android.server.credentials.metrics.ProviderStatusForMetrics; import java.util.ArrayList; +import java.util.Set; import java.util.stream.Collectors; /** @@ -45,22 +46,26 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, IGetCredentialCallback, GetCredentialResponse> implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> { private static final String TAG = "GetRequestSession"; + 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, sessionCallback, lock, userId, callingUid, request, callback, - RequestInfo.TYPE_GET, callingAppInfo, cancellationSignal, startedTimestamp); + 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. */ @@ -72,7 +77,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, .createNewSession(mContext, mUserId, providerInfo, this, remoteCredentialService); if (providerGetSession != null) { - Log.i(TAG, "In startProviderSession - provider session created and being added"); + Slog.d(TAG, "In startProviderSession - provider session created and " + + "being added for: " + providerInfo.getComponentName()); mProviders.put(providerGetSession.getComponentName().flattenToString(), providerGetSession); } @@ -111,7 +117,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onFinalResponseReceived(ComponentName componentName, @Nullable GetCredentialResponse response) { - Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString()); + Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString()); mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime()); mRequestSessionMetric.collectChosenMetricViaCandidateTransfer( mProviders.get(componentName.flattenToString()) @@ -149,13 +155,13 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onUiSelectorInvocationFailure() { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, - "No credentials available."); + "No credentials available."); } @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source); + Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { @@ -168,7 +174,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, // or we need to respond with error. The only other case is the entry being // selected after the UI has been invoked which has a separate code path. if (isUiInvocationNeeded()) { - Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); + Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded"); getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL, diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java index 88f3e6c92947..9e7a87e74522 100644 --- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java @@ -33,7 +33,6 @@ import android.os.CancellationSignal; import android.os.RemoteException; import android.service.credentials.CallingAppInfo; import android.service.credentials.PermissionUtils; -import android.util.Log; import android.util.Slog; import java.util.ArrayList; @@ -52,10 +51,11 @@ public class PrepareGetRequestSession extends GetRequestSession { public PrepareGetRequestSession(Context context, RequestSession.SessionLifetime sessionCallback, Object lock, int userId, int callingUid, IGetCredentialCallback getCredCallback, GetCredentialRequest request, - CallingAppInfo callingAppInfo, CancellationSignal cancellationSignal, - long startedTimestamp, IPrepareGetCredentialCallback prepareGetCredentialCallback) { + CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders, + CancellationSignal cancellationSignal, long startedTimestamp, + IPrepareGetCredentialCallback prepareGetCredentialCallback) { super(context, sessionCallback, lock, userId, callingUid, getCredCallback, request, - callingAppInfo, cancellationSignal, startedTimestamp); + callingAppInfo, enabledProviders, cancellationSignal, startedTimestamp); int numTypes = (request.getCredentialOptions().stream() .map(CredentialOption::getType).collect( Collectors.toSet())).size(); // Dedupe type strings @@ -66,6 +66,9 @@ public class PrepareGetRequestSession extends GetRequestSession { @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { + Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and " + + "source: " + source); + switch (source) { case REMOTE_PROVIDER: // Remote provider's status changed. We should check if all providers are done, and @@ -122,7 +125,7 @@ public class PrepareGetRequestSession extends GetRequestSession { hasPermission, credentialTypes, hasAuthenticationResults, hasRemoteResults, uiIntent)); } catch (RemoteException e) { - Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); + Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); } } @@ -137,7 +140,7 @@ public class PrepareGetRequestSession extends GetRequestSession { /*hasRemoteResults=*/ false, /*pendingIntent=*/ null)); } catch (RemoteException e) { - Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); + Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e); } } @@ -178,10 +181,8 @@ public class PrepareGetRequestSession extends GetRequestSession { private PendingIntent getUiIntent() { ArrayList<ProviderData> providerDataList = new ArrayList<>(); for (ProviderSession session : mProviders.values()) { - Log.i(TAG, "preparing data for : " + session.getComponentName()); ProviderData providerData = session.prepareUiData(); if (providerData != null) { - Log.i(TAG, "Provider data is not null"); providerDataList.add(providerData); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index eaf58f13e2fa..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() { @@ -126,8 +132,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mProviderCancellationSignal = - mRemoteCredentialService.onClearCredentialState(mProviderRequest, this); + mRemoteCredentialService.onClearCredentialState(mProviderRequest, this); } } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index c657e3b1b389..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,8 +242,7 @@ public final class ProviderCreateSession extends ProviderSession< protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mProviderCancellationSignal = - 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 9c9c0c212d0c..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) { @@ -302,9 +308,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential protected void invokeSession() { if (mRemoteCredentialService != null) { startCandidateMetrics(); - mProviderCancellationSignal = - mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this); - boolean foundSig = mProviderCancellationSignal == null; + mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this); } } 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/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 ed175ed9601f..8fd02691e190 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -32,7 +32,6 @@ 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; @@ -43,6 +42,7 @@ import com.android.server.credentials.metrics.RequestSessionMetric; import java.util.ArrayList; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -88,6 +88,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential protected final SessionLifetime mSessionCallback; + private final Set<ComponentName> mEnabledProviders; + @NonNull protected RequestSessionStatus mRequestSessionStatus = RequestSessionStatus.IN_PROGRESS; @@ -108,6 +110,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential @NonNull T clientRequest, U clientCallback, @NonNull String requestType, CallingAppInfo callingAppInfo, + Set<ComponentName> enabledProviders, CancellationSignal cancellationSignal, long timestampStarted) { mContext = context; mLock = lock; @@ -118,11 +121,12 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential 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, @@ -174,7 +178,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential @Override // from CredentialManagerUiCallbacks public void onUiSelection(UserSelectionDialogResult selection) { if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -182,13 +186,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential return; } String providerId = selection.getProviderId(); - Log.i(TAG, "onUiSelection, providerId: " + providerId); ProviderSession providerSession = mProviders.get(providerId); if (providerSession == null) { - Log.i(TAG, "providerSession not found in onUiSelection"); + Slog.w(TAG, "providerSession not found in onUiSelection. This is strange."); return; } - Log.i(TAG, "Provider session found"); mRequestSessionMetric.collectMetricPerBrowsingSelect(selection, providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric()); providerSession.onUiEntrySelected(selection.getEntryKey(), @@ -242,15 +244,13 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential void getProviderDataAndInitiateUi() { ArrayList<ProviderData> providerDataList = getProviderDataForUi(); if (!providerDataList.isEmpty()) { - Log.i(TAG, "provider list not empty about to initiate ui"); launchUiWithProviderData(providerDataList); } } @NonNull protected ArrayList<ProviderData> getProviderDataForUi() { - Log.i(TAG, "In getProviderDataAndInitiateUi"); - Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); + Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size()); ArrayList<ProviderData> providerDataList = new ArrayList<>(); mRequestSessionMetric.logCandidatePhaseMetrics(mProviders); @@ -260,10 +260,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } for (ProviderSession session : mProviders.values()) { - Log.i(TAG, "preparing data for : " + session.getComponentName()); ProviderData providerData = session.prepareUiData(); if (providerData != null) { - Log.i(TAG, "Provider data is not null"); providerDataList.add(providerData); } } @@ -279,7 +277,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false, ProviderStatusForMetrics.FINAL_SUCCESS); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -295,7 +293,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential } catch (RemoteException e) { mRequestSessionMetric.collectFinalPhaseProviderMetricStatus( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); - Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage()); + Slog.e(TAG, "Issue while responding to client with a response : " + e); mRequestSessionMetric.logApiCalledAtFinish( /*apiStatus=*/ ApiStatus.FAILURE.getMetricCode()); } @@ -306,13 +304,13 @@ 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( /*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE); if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) { - Log.i(TAG, "Request has already been completed. This is strange."); + Slog.w(TAG, "Request has already been completed. This is strange."); return; } if (isSessionCancelled()) { @@ -325,7 +323,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential try { invokeClientCallbackError(errorType, errorMsg); } catch (RemoteException e) { - Log.i(TAG, "Issue while responding to client with error : " + e.getMessage()); + Slog.e(TAG, "Issue while responding to client with error : " + e); } boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING); mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java index 474df98cb1e6..950ec77f5ba8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java @@ -32,22 +32,25 @@ import java.util.Objects; final class BooleanPolicySerializer extends PolicySerializer<Boolean> { + private static final String ATTR_VALUE = "value"; + + private static final String TAG = "BooleanPolicySerializer"; + @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName, - @NonNull Boolean value) + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value) throws IOException { Objects.requireNonNull(value); - serializer.attributeBoolean(/* namespace= */ null, attributeName, value); + serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value); } @Nullable @Override - BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { + BooleanPolicyValue readFromXml(TypedXmlPullParser parser) { try { return new BooleanPolicyValue( - parser.getAttributeBoolean(/* namespace= */ null, attributeName)); + parser.getAttributeBoolean(/* namespace= */ null, ATTR_VALUE)); } catch (XmlPullParserException e) { - Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e); + Log.e(TAG, "Error parsing Boolean policy value", e); return null; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java index c79aac722bd7..ee73f8afabd2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -53,6 +53,10 @@ import java.util.Objects; // rather than in its own files. final class BundlePolicySerializer extends PolicySerializer<Bundle> { + private static final String TAG = "BundlePolicySerializer"; + + private static final String ATTR_FILE_NAME = "file-name"; + private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_"; private static final String XML_SUFFIX = ".xml"; @@ -72,7 +76,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { @Override void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer, - String attributeName, @NonNull Bundle value) throws IOException { + @NonNull Bundle value) throws IOException { Objects.requireNonNull(value); Objects.requireNonNull(policyKey); if (!(policyKey instanceof PackagePolicyKey)) { @@ -82,13 +86,13 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { String packageName = ((PackagePolicyKey) policyKey).getPackageName(); String fileName = packageToRestrictionsFileName(packageName, value); writeApplicationRestrictionsLAr(fileName, value); - serializer.attribute(/* namespace= */ null, attributeName, fileName); + serializer.attribute(/* namespace= */ null, ATTR_FILE_NAME, fileName); } @Nullable @Override - BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { - String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName); + BundlePolicyValue readFromXml(TypedXmlPullParser parser) { + String fileName = parser.getAttributeValue(/* namespace= */ null, ATTR_FILE_NAME); return new BundlePolicyValue(readApplicationRestrictions(fileName)); } @@ -119,7 +123,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { final TypedXmlPullParser parser = Xml.resolvePullParser(fis); XmlUtils.nextElement(parser); if (parser.getEventType() != XmlPullParser.START_TAG) { - Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file " + Slog.e(TAG, "Unable to read restrictions file " + restrictionsFile.getBaseFile()); return restrictions; } @@ -127,7 +131,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { readEntry(restrictions, values, parser); } } catch (IOException | XmlPullParserException e) { - Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e); + Slog.w(TAG, "Error parsing " + restrictionsFile.getBaseFile(), e); } finally { IoUtils.closeQuietly(fis); } @@ -209,7 +213,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { restrictionsFile.finishWrite(fos); } catch (Exception e) { restrictionsFile.failWrite(fos); - Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e); + Slog.e(TAG, "Error writing application restrictions list", e); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java index d1c6bcb8b48c..6303a1a8b860 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java @@ -30,30 +30,31 @@ import java.io.IOException; import java.util.Objects; final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName> { - private static final String ATTR_PACKAGE_NAME = ":package-name"; - private static final String ATTR_CLASS_NAME = ":class-name"; + + private static final String TAG = "ComponentNamePolicySerializer"; + + private static final String ATTR_PACKAGE_NAME = "package-name"; + private static final String ATTR_CLASS_NAME = "class-name"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull ComponentName value) throws IOException { Objects.requireNonNull(value); serializer.attribute( - /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGE_NAME, value.getPackageName()); + /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName()); serializer.attribute( - /* namespace= */ null, - attributeNamePrefix + ATTR_CLASS_NAME, value.getClassName()); + /* namespace= */ null, ATTR_CLASS_NAME, value.getClassName()); } @Nullable @Override - ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { + ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser) { String packageName = parser.getAttributeValue( - /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME); + /* namespace= */ null, ATTR_PACKAGE_NAME); String className = parser.getAttributeValue( - /* namespace= */ null, attributeNamePrefix + ATTR_CLASS_NAME); + /* namespace= */ null, ATTR_CLASS_NAME); if (packageName == null || className == null) { - Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy."); + Log.e(TAG, "Error parsing ComponentName policy."); return null; } return new ComponentNamePolicyValue(new ComponentName(packageName, className)); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d4f4b72fbb2b..f111a9541303 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -1181,7 +1181,8 @@ final class DevicePolicyEngine { private static final String DEVICE_POLICIES_XML = "device_policy_state.xml"; private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry"; private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry"; - private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry"; + private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry"; + private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry"; private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry"; private static final String ATTR_USER_ID = "user-id"; @@ -1236,11 +1237,14 @@ final class DevicePolicyEngine { serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY); serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId); + + serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); policy.getKey().saveToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); - serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY); + serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); policy.getValue().saveToXml(serializer); - serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY); + serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY); } @@ -1253,11 +1257,13 @@ final class DevicePolicyEngine { for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) { serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY); + serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); policy.getKey().saveToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY); - serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY); + serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); policy.getValue().saveToXml(serializer); - serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY); + serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY); serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY); } @@ -1323,28 +1329,56 @@ final class DevicePolicyEngine { private void readLocalPoliciesInner(TypedXmlPullParser parser) throws XmlPullParserException, IOException { int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID); - PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); - if (!mLocalPolicies.contains(userId)) { - mLocalPolicies.put(userId, new HashMap<>()); + PolicyKey policyKey = null; + PolicyState<?> policyState = null; + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + String tag = parser.getName(); + switch (tag) { + case TAG_POLICY_KEY_ENTRY: + policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); + break; + case TAG_POLICY_STATE_ENTRY: + policyState = PolicyState.readFromXml(parser); + break; + default: + Log.e(TAG, "Unknown tag for local policy entry" + tag); + } } - PolicyState<?> adminsPolicy = parseAdminsPolicy(parser); - if (adminsPolicy != null) { - mLocalPolicies.get(userId).put(policyKey, adminsPolicy); + + if (policyKey != null && policyState != null) { + if (!mLocalPolicies.contains(userId)) { + mLocalPolicies.put(userId, new HashMap<>()); + } + mLocalPolicies.get(userId).put(policyKey, policyState); } else { - Log.e(TAG, - "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy."); + Log.e(TAG, "Error parsing local policy"); } } private void readGlobalPoliciesInner(TypedXmlPullParser parser) throws IOException, XmlPullParserException { - PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); - PolicyState<?> adminsPolicy = parseAdminsPolicy(parser); - if (adminsPolicy != null) { - mGlobalPolicies.put(policyKey, adminsPolicy); + PolicyKey policyKey = null; + PolicyState<?> policyState = null; + int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + String tag = parser.getName(); + switch (tag) { + case TAG_POLICY_KEY_ENTRY: + policyKey = PolicyDefinition.readPolicyKeyFromXml(parser); + break; + case TAG_POLICY_STATE_ENTRY: + policyState = PolicyState.readFromXml(parser); + break; + default: + Log.e(TAG, "Unknown tag for local policy entry" + tag); + } + } + + if (policyKey != null && policyState != null) { + mGlobalPolicies.put(policyKey, policyState); } else { - Log.e(TAG, - "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy."); + Log.e(TAG, "Error parsing global policy"); } } @@ -1356,20 +1390,5 @@ final class DevicePolicyEngine { } mEnforcingAdmins.get(admin.getUserId()).add(admin); } - - @Nullable - private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser) - throws XmlPullParserException, IOException { - int outerDepth = parser.getDepth(); - while (XmlUtils.nextElementWithin(parser, outerDepth)) { - String tag = parser.getName(); - if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) { - return PolicyState.readFromXml(parser); - } - Log.e(TAG, "Unknown tag " + tag); - } - Log.e(TAG, "Error parsing file, AdminsPolicy not found"); - return null; - } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b2156414b122..94e6e732dd76 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -563,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 @@ -10380,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()) @@ -11387,7 +11391,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (PolicyKey key : keys) { if (!(key instanceof IntentFilterPolicyKey)) { throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not" - + "of type PersistentPreferredActivityPolicyKey"); + + "of type IntentFilterPolicyKey"); } IntentFilterPolicyKey parsedKey = (IntentFilterPolicyKey) key; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java index bff6d3288dca..45a2d2a7bda1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java @@ -32,21 +32,25 @@ import java.util.Objects; final class IntegerPolicySerializer extends PolicySerializer<Integer> { + private static final String TAG = "IntegerPolicySerializer"; + + private static final String ATTR_VALUE = "value"; + @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName, + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Integer value) throws IOException { Objects.requireNonNull(value); - serializer.attributeInt(/* namespace= */ null, attributeName, value); + serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value); } @Nullable @Override - IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { + IntegerPolicyValue readFromXml(TypedXmlPullParser parser) { try { return new IntegerPolicyValue( - parser.getAttributeInt(/* namespace= */ null, attributeName)); + parser.getAttributeInt(/* namespace= */ null, ATTR_VALUE)); } catch (XmlPullParserException e) { - Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e); + Log.e(TAG, "Error parsing Integer policy value", e); return null; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java index 3265b61a3543..0f6f3c5a5921 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java @@ -32,12 +32,14 @@ import java.util.Set; final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { - private static final String ATTR_PACKAGES = ":packages"; + private static final String TAG = "LockTaskPolicySerializer"; + + private static final String ATTR_PACKAGES = "packages"; private static final String ATTR_PACKAGES_SEPARATOR = ";"; - private static final String ATTR_FLAGS = ":flags"; + private static final String ATTR_FLAGS = "flags"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull LockTaskPolicy value) throws IOException { Objects.requireNonNull(value); if (value.getPackages() == null || value.getPackages().isEmpty()) { @@ -46,31 +48,31 @@ final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { } serializer.attribute( /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGES, + ATTR_PACKAGES, String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages())); serializer.attributeInt( /* namespace= */ null, - attributeNamePrefix + ATTR_FLAGS, + ATTR_FLAGS, value.getFlags()); } @Override - LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { + LockTaskPolicy readFromXml(TypedXmlPullParser parser) { String packagesStr = parser.getAttributeValue( /* namespace= */ null, - attributeNamePrefix + ATTR_PACKAGES); + ATTR_PACKAGES); if (packagesStr == null) { - Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value."); + Log.e(TAG, "Error parsing LockTask policy value."); return null; } Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR)); try { int flags = parser.getAttributeInt( /* namespace= */ null, - attributeNamePrefix + ATTR_FLAGS); + ATTR_FLAGS); return new LockTaskPolicy(packages, flags); } catch (XmlPullParserException e) { - Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e); + Log.e(TAG, "Error parsing LockTask policy value", e); return null; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java index f77d051cab86..522c4b5e84be 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java @@ -32,21 +32,25 @@ import java.util.Objects; final class LongPolicySerializer extends PolicySerializer<Long> { + private static final String TAG = "LongPolicySerializer"; + + private static final String ATTR_VALUE = "value"; + @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName, + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Long value) throws IOException { Objects.requireNonNull(value); - serializer.attributeLong(/* namespace= */ null, attributeName, value); + serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value); } @Nullable @Override - LongPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) { + LongPolicyValue readFromXml(TypedXmlPullParser parser) { try { return new LongPolicyValue( - parser.getAttributeLong(/* namespace= */ null, attributeName)); + parser.getAttributeLong(/* namespace= */ null, ATTR_VALUE)); } catch (XmlPullParserException e) { - Log.e(DevicePolicyEngine.TAG, "Error parsing Long policy value", e); + Log.e(TAG, "Error parsing Long policy value", e); return null; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index a15aa53be96c..509a66b4f8f7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -531,7 +531,6 @@ final class PolicyDefinition<V> { } void saveToXml(TypedXmlSerializer serializer) throws IOException { - // TODO: here and elsewhere, add tags to ensure attributes aren't overridden by duplication. mPolicyKey.saveToXml(serializer); } @@ -554,14 +553,14 @@ final class PolicyDefinition<V> { return genericPolicyDefinition.mPolicyKey.readFromXml(parser); } - void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value) + void savePolicyValueToXml(TypedXmlSerializer serializer, V value) throws IOException { - mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value); + mPolicySerializer.saveToXml(mPolicyKey, serializer, value); } @Nullable - PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) { - return mPolicySerializer.readFromXml(parser, attributeName); + PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser) { + return mPolicySerializer.readFromXml(parser); } @Override diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java index 0ef431f6ff90..5af2fa285483 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java @@ -26,8 +26,7 @@ import com.android.modules.utils.TypedXmlSerializer; import java.io.IOException; abstract class PolicySerializer<V> { - abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - String attributeName, @NonNull V value) + abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value) throws IOException; - abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName); + abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java index 3a792d82d2ba..741f209a90c3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java @@ -35,11 +35,14 @@ import java.util.Objects; * Class containing all values set for a certain policy by different admins. */ final class PolicyState<V> { + + private static final String TAG = "PolicyState"; private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry"; - private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry"; - private static final String ATTR_POLICY_VALUE = "policy-value"; - private static final String ATTR_RESOLVED_POLICY = "resolved-policy"; + private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry"; + private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry"; + private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry"; + private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry"; private final PolicyDefinition<V> mPolicyDefinition; private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins = new LinkedHashMap<>(); @@ -193,18 +196,24 @@ final class PolicyState<V> { } void saveToXml(TypedXmlSerializer serializer) throws IOException { + serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY); mPolicyDefinition.saveToXml(serializer); + serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY); if (mCurrentResolvedPolicy != null) { + serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY); mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue()); + serializer, mCurrentResolvedPolicy.getValue()); + serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY); } for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) { serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY); + serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY); mPolicyDefinition.savePolicyValueToXml( - serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue()); + serializer, mPoliciesSetByAdmins.get(admin).getValue()); + serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY); serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY); admin.saveToXml(serializer); @@ -217,32 +226,57 @@ final class PolicyState<V> { static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser) throws IOException, XmlPullParserException { - PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser); + PolicyDefinition<V> policyDefinition = null; - PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml( - parser, ATTR_RESOLVED_POLICY); + PolicyValue<V> currentResolvedPolicy = null; LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>(); int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { String tag = parser.getName(); - if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) { - PolicyValue<V> value = policyDefinition.readPolicyValueFromXml( - parser, ATTR_POLICY_VALUE); - EnforcingAdmin admin; - int adminPolicyDepth = parser.getDepth(); - if (XmlUtils.nextElementWithin(parser, adminPolicyDepth) - && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) { - admin = EnforcingAdmin.readFromXml(parser); - policiesSetByAdmins.put(admin, value); - } - } else { - Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag); + switch (tag) { + case TAG_ADMIN_POLICY_ENTRY: + PolicyValue<V> value = null; + EnforcingAdmin admin = null; + int adminPolicyDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) { + String adminPolicyTag = parser.getName(); + switch (adminPolicyTag) { + case TAG_ENFORCING_ADMIN_ENTRY: + admin = EnforcingAdmin.readFromXml(parser); + break; + case TAG_POLICY_VALUE_ENTRY: + value = policyDefinition.readPolicyValueFromXml(parser); + break; + } + } + if (admin != null && value != null) { + policiesSetByAdmins.put(admin, value); + } else { + Log.e(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY"); + } + break; + case TAG_POLICY_DEFINITION_ENTRY: + policyDefinition = PolicyDefinition.readFromXml(parser); + break; + + case TAG_RESOLVED_VALUE_ENTRY: + currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser); + break; + default: + Log.e(TAG, "Unknown tag: " + tag); } } - return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy); + if (policyDefinition != null) { + return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy); + } else { + Log.e("PolicyState", "Error parsing policyState"); + return null; + } } + + PolicyDefinition<V> getPolicyDefinition() { return mPolicyDefinition; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java index dc6592d73116..24d0521471f7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java @@ -36,21 +36,17 @@ final class StringSetPolicySerializer extends PolicySerializer<Set<String>> { private static final String ATTR_VALUES_SEPARATOR = ";"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix, + void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Set<String> value) throws IOException { Objects.requireNonNull(value); serializer.attribute( - /* namespace= */ null, - attributeNamePrefix + ATTR_VALUES, - String.join(ATTR_VALUES_SEPARATOR, value)); + /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value)); } @Nullable @Override - PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) { - String valuesStr = parser.getAttributeValue( - /* namespace= */ null, - attributeNamePrefix + ATTR_VALUES); + PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) { + String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES); if (valuesStr == null) { Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value."); return null; 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 318067ee8681..8211d6fc03a2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -406,7 +406,7 @@ public final class BroadcastQueueModernImplTest { assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt); assertTrue(queue.isRunnable()); assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason()); - assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked()); + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); } /** @@ -434,13 +434,13 @@ public final class BroadcastQueueModernImplTest { queue.setProcessAndUidCached(null, false); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); - assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); queue.setProcessAndUidCached(null, true); assertTrue(queue.isRunnable()); assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime); - assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord); } @@ -1154,6 +1154,41 @@ public final class BroadcastQueueModernImplTest { times(1)); } + @Test + public void testGetPreferredSchedulingGroup() throws Exception { + final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, + List.of(makeMockRegisteredReceiver())), 0, false); + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); + + // Make the foreground broadcast as active. + queue.makeActiveNextPending(); + assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); + + queue.makeActiveIdle(); + assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane, + List.of(makeMockRegisteredReceiver())), 0, false); + + // Make the background broadcast as active. + queue.makeActiveNextPending(); + assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked()); + + queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, + List.of(makeMockRegisteredReceiver())), 0, false); + // Even though the active broadcast is not a foreground one, scheduling group will be + // DEFAULT since there is a foreground broadcast waiting to be delivered. + assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); + } + private Intent createPackageChangedIntent(int uid, List<String> componentNameList) { final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); packageChangedIntent.putExtra(Intent.EXTRA_UID, uid); 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/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 6216c66aa54f..4b86dd048cd1 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -152,8 +152,7 @@ public class AuthServiceTest { verify(mBiometricService, never()).registerAuthenticator( anyInt(), - anyInt(), - anyInt(), + any(), any()); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index 4cdca268fc4b..dbf5021d3c6b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -44,13 +44,14 @@ import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.Context; import android.hardware.biometrics.BiometricManager.Authenticators; -import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Binder; @@ -458,9 +459,16 @@ public class AuthSessionTest { IBiometricAuthenticator fingerprintAuthenticator = mock(IBiometricAuthenticator.class); when(fingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); when(fingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - mSensors.add(new BiometricSensor(mContext, id, + + final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal( + id, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, type, + false /* resetLockoutRequiresHardwareAuthToken */); + mFingerprintSensorProps.add(props); + + mSensors.add(new BiometricSensor(mContext, TYPE_FINGERPRINT /* modality */, - Authenticators.BIOMETRIC_STRONG /* strength */, + props, fingerprintAuthenticator) { @Override boolean confirmationAlwaysRequired(int userId) { @@ -473,21 +481,6 @@ public class AuthSessionTest { } }); - final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - mFingerprintSensorProps.add(new FingerprintSensorPropertiesInternal(id, - SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, - componentInfo, - type, - false /* resetLockoutRequiresHardwareAuthToken */)); - when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); } @@ -495,9 +488,13 @@ public class AuthSessionTest { IBiometricAuthenticator authenticator) throws RemoteException { when(authenticator.isHardwareDetected(any())).thenReturn(true); when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - mSensors.add(new BiometricSensor(mContext, id, + mSensors.add(new BiometricSensor(mContext, TYPE_FACE /* modality */, - Authenticators.BIOMETRIC_STRONG /* strength */, + new FaceSensorPropertiesInternal(id, + SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, FaceSensorProperties.TYPE_UNKNOWN, + true /* supportsFace Detection */, true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */), authenticator) { @Override boolean confirmationAlwaysRequired(int userId) { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index 168642e3533f..b51a8c4e1b6c 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; @@ -66,7 +67,11 @@ import android.hardware.biometrics.IBiometricServiceReceiver; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManagerGlobal; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; +import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -93,6 +98,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; import java.util.Random; @Presubmit @@ -114,6 +120,7 @@ public class BiometricServiceTest { private static final int SENSOR_ID_FINGERPRINT = 0; private static final int SENSOR_ID_FACE = 1; + private FingerprintSensorPropertiesInternal mFingerprintProps; private BiometricService mBiometricService; @@ -193,6 +200,11 @@ public class BiometricServiceTest { }; when(mInjector.getConfiguration(any())).thenReturn(config); + + mFingerprintProps = new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT, + STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */); } @Test @@ -328,8 +340,7 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); - mBiometricService.mImpl.registerAuthenticator(0 /* id */, - TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps, mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, @@ -401,8 +412,7 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); - mBiometricService.mImpl.registerAuthenticator(0 /* id */, - TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps, mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, @@ -1334,9 +1344,13 @@ public class BiometricServiceTest { for (int i = 0; i < testCases.length; i++) { final BiometricSensor sensor = - new BiometricSensor(mContext, 0 /* id */, + new BiometricSensor(mContext, TYPE_FINGERPRINT, - testCases[i][0], + new FingerprintSensorPropertiesInternal(i /* id */, + Utils.authenticatorStrengthToPropertyStrength(testCases[i][0]), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */), mock(IBiometricAuthenticator.class)) { @Override boolean confirmationAlwaysRequired(int userId) { @@ -1364,8 +1378,7 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())) .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); - mBiometricService.mImpl.registerAuthenticator(0 /* testId */, - TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps, mFingerprintAuthenticator); verify(mBiometricService.mBiometricStrengthController).updateStrengths(); @@ -1376,15 +1389,14 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); - final int testId = 0; - when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())) .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); - mBiometricService.mImpl.registerAuthenticator(testId /* id */, - TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + + final int testId = SENSOR_ID_FINGERPRINT; + mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps, mFingerprintAuthenticator); // Downgrade the authenticator @@ -1484,11 +1496,9 @@ public class BiometricServiceTest { mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator( - 0 /* id */, 2 /* modality */, 15 /* strength */, - mFingerprintAuthenticator); + 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator); mBiometricService.mImpl.registerAuthenticator( - 0 /* id */, 2 /* modality */, 15 /* strength */, - mFingerprintAuthenticator); + 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator); } @Test(expected = IllegalArgumentException.class) @@ -1498,9 +1508,7 @@ public class BiometricServiceTest { mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator( - 0 /* id */, 2 /* modality */, - Authenticators.BIOMETRIC_STRONG /* strength */, - null /* authenticator */); + 2 /* modality */, mFingerprintProps, null /* authenticator */); } @Test @@ -1511,8 +1519,13 @@ public class BiometricServiceTest { for (String s : mInjector.getConfiguration(null)) { SensorConfig config = new SensorConfig(s); - mBiometricService.mImpl.registerAuthenticator(config.id, config.modality, - config.strength, mFingerprintAuthenticator); + mBiometricService.mImpl.registerAuthenticator(config.modality, + new FingerprintSensorPropertiesInternal(config.id, + Utils.authenticatorStrengthToPropertyStrength(config.strength), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */), + mFingerprintAuthenticator); } } @@ -1609,7 +1622,12 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(LockoutTracker.LOCKOUT_NONE); - mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality, strength, + mBiometricService.mImpl.registerAuthenticator(modality, + new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT, + Utils.authenticatorStrengthToPropertyStrength(strength), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */), mFingerprintAuthenticator); } @@ -1618,7 +1636,13 @@ public class BiometricServiceTest { when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); when(mFaceAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(LockoutTracker.LOCKOUT_NONE); - mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength, + mBiometricService.mImpl.registerAuthenticator(modality, + new FaceSensorPropertiesInternal(SENSOR_ID_FACE, + Utils.authenticatorStrengthToPropertyStrength(strength), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FaceSensorProperties.TYPE_UNKNOWN, true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */), mFaceAuthenticator); } } @@ -1641,15 +1665,27 @@ public class BiometricServiceTest { when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())) .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); - mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality, - strength, mFingerprintAuthenticator); + mBiometricService.mImpl.registerAuthenticator(modality, + new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT, + Utils.authenticatorStrengthToPropertyStrength(strength), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UNKNOWN, + false /* resetLockoutRequiresHardwareAuthToken */), + mFingerprintAuthenticator); } if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) { when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true); - mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, - strength, mFaceAuthenticator); + mBiometricService.mImpl.registerAuthenticator(modality, + new FaceSensorPropertiesInternal(SENSOR_ID_FACE, + Utils.authenticatorStrengthToPropertyStrength(strength), + 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */, + FaceSensorProperties.TYPE_UNKNOWN, + true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */), + mFaceAuthenticator); } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java index ee5ab92065ee..f7539bd27c9d 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java @@ -16,6 +16,9 @@ package com.android.server.biometrics; +import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -27,9 +30,13 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IInvalidationCallback; +import android.hardware.biometrics.SensorPropertiesInternal; +import android.hardware.face.FaceSensorProperties; +import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintSensorProperties; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -42,6 +49,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; @Presubmit @SmallTest @@ -59,26 +67,54 @@ public class InvalidationTrackerTest { public void testCallbackReceived_whenAllStrongSensorsInvalidated() throws Exception { final IBiometricAuthenticator authenticator1 = mock(IBiometricAuthenticator.class); when(authenticator1.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - final TestSensor sensor1 = new TestSensor(mContext, 0 /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + final TestSensor sensor1 = new TestSensor(mContext, + BiometricAuthenticator.TYPE_FINGERPRINT, + new FingerprintSensorPropertiesInternal(0 /* id */, + STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + false /* resetLockoutRequiresHardwareAuthToken */), authenticator1); final IBiometricAuthenticator authenticator2 = mock(IBiometricAuthenticator.class); when(authenticator2.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - final TestSensor sensor2 = new TestSensor(mContext, 1 /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + final TestSensor sensor2 = new TestSensor(mContext, + BiometricAuthenticator.TYPE_FINGERPRINT, + new FingerprintSensorPropertiesInternal(1 /* id */, + STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, + FingerprintSensorProperties.TYPE_REAR, + false /* resetLockoutRequiresHardwareAuthToken */), authenticator2); final IBiometricAuthenticator authenticator3 = mock(IBiometricAuthenticator.class); when(authenticator3.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - final TestSensor sensor3 = new TestSensor(mContext, 2 /* id */, - BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG, + final TestSensor sensor3 = new TestSensor(mContext, + BiometricAuthenticator.TYPE_FACE, + new FaceSensorPropertiesInternal(2 /* id */, + STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, + FaceSensorProperties.TYPE_RGB, + true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */), authenticator3); final IBiometricAuthenticator authenticator4 = mock(IBiometricAuthenticator.class); when(authenticator4.hasEnrolledTemplates(anyInt(), any())).thenReturn(true); - final TestSensor sensor4 = new TestSensor(mContext, 3 /* id */, - BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK, + final TestSensor sensor4 = new TestSensor(mContext, + BiometricAuthenticator.TYPE_FACE, + new FaceSensorPropertiesInternal(3 /* id */, + STRENGTH_WEAK, + 5 /* maxEnrollmentsPerUser */, + List.of() /* componentInfo */, + FaceSensorProperties.TYPE_IR, + true /* supportsFace Detection */, + true /* supportsSelfIllumination */, + false /* resetLockoutRequiresHardwareAuthToken */), authenticator4); final ArrayList<BiometricSensor> sensors = new ArrayList<>(); @@ -113,9 +149,9 @@ public class InvalidationTrackerTest { private static class TestSensor extends BiometricSensor { - TestSensor(@NonNull Context context, int id, int modality, int strength, + TestSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props, @NonNull IBiometricAuthenticator impl) { - super(context, id, modality, strength, impl); + super(context, modality, props, impl); } @Override diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java index 903ed9082481..d3f04dfcfa17 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java @@ -17,8 +17,6 @@ package com.android.server.biometrics.sensors.face; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; -import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; -import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; @@ -70,9 +68,7 @@ public class FaceServiceRegistryTest { @Mock private ServiceProvider mProvider2; @Captor - private ArgumentCaptor<Integer> mIdCaptor; - @Captor - private ArgumentCaptor<Integer> mStrengthCaptor; + private ArgumentCaptor<FaceSensorPropertiesInternal> mPropsCaptor; private FaceSensorPropertiesInternal mProvider1Props; private FaceSensorPropertiesInternal mProvider2Props; @@ -82,13 +78,13 @@ public class FaceServiceRegistryTest { public void setup() { mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1, STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, - List.of(), FaceSensorProperties.TYPE_RGB, + List.of() /* componentInfo */, FaceSensorProperties.TYPE_RGB, true /* supportsFace Detection */, true /* supportsSelfIllumination */, false /* resetLockoutRequiresHardwareAuthToken */); mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2, STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, - List.of(), FaceSensorProperties.TYPE_IR, + List.of() /* componentInfo */, FaceSensorProperties.TYPE_IR, true /* supportsFace Detection */, true /* supportsSelfIllumination */, false /* resetLockoutRequiresHardwareAuthToken */); @@ -107,10 +103,9 @@ public class FaceServiceRegistryTest { assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2); assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props); verify(mBiometricService, times(2)).registerAuthenticator( - mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any()); - assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2); - assertThat(mStrengthCaptor.getAllValues()) - .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG); + eq(TYPE_FACE), mPropsCaptor.capture(), any()); + assertThat(mPropsCaptor.getAllValues()) + .containsExactly(mProvider1Props, mProvider2Props); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java index 13c3f64fec93..6e09069e654b 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java @@ -17,8 +17,6 @@ package com.android.server.biometrics.sensors.fingerprint; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; -import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG; -import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK; @@ -70,9 +68,7 @@ public class FingerprintServiceRegistryTest { @Mock private ServiceProvider mProvider2; @Captor - private ArgumentCaptor<Integer> mIdCaptor; - @Captor - private ArgumentCaptor<Integer> mStrengthCaptor; + private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor; private FingerprintSensorPropertiesInternal mProvider1Props; private FingerprintSensorPropertiesInternal mProvider2Props; @@ -82,11 +78,11 @@ public class FingerprintServiceRegistryTest { public void setup() { mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1, STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */, - List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* resetLockoutRequiresHardwareAuthToken */); mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2, STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, - List.of(), FingerprintSensorProperties.TYPE_UNKNOWN, + List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UNKNOWN, false /* resetLockoutRequiresHardwareAuthToken */); when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props)); @@ -103,10 +99,9 @@ public class FingerprintServiceRegistryTest { assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2); assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props); verify(mBiometricService, times(2)).registerAuthenticator( - mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any()); - assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2); - assertThat(mStrengthCaptor.getAllValues()) - .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG); + eq(TYPE_FINGERPRINT), mPropsCaptor.capture(), any()); + assertThat(mPropsCaptor.getAllValues()) + .containsExactly(mProvider1Props, mProvider2Props); } @Test diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java index 25a700a5275f..1089c07e6787 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java @@ -110,15 +110,17 @@ public class FingerprintServiceTest { private final FingerprintSensorPropertiesInternal mSensorPropsDefault = new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG, 2 /* maxEnrollmentsPerUser */, - List.of(), + List.of() /* componentInfo */, TYPE_REAR, false /* resetLockoutRequiresHardwareAuthToken */); private final FingerprintSensorPropertiesInternal mSensorPropsVirtual = new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG, 2 /* maxEnrollmentsPerUser */, - List.of(), + List.of() /* componentInfo */, TYPE_UDFPS_OPTICAL, false /* resetLockoutRequiresHardwareAuthToken */); + @Captor + private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor; private FingerprintService mService; @Before @@ -166,7 +168,8 @@ public class FingerprintServiceTest { mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); - verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any()); + verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any()); + assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsDefault); } @Test @@ -178,7 +181,8 @@ public class FingerprintServiceTest { mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); - verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); + verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any()); + assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual); } @Test @@ -188,7 +192,8 @@ public class FingerprintServiceTest { mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS); waitForRegistration(); - verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any()); + verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any()); + assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual); } private void waitForRegistration() throws Exception { 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 ce4b438470df..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; @@ -93,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)); @@ -120,32 +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(new ArrayList<ThrottlingLevel>()); + data = ThermalBrightnessThrottlingData.create(new ArrayList<ThrottlingLevel>()); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedThermalLevels); + data = ThermalBrightnessThrottlingData.create(unsortedThermalLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedBrightnessLevels); + data = ThermalBrightnessThrottlingData.create(unsortedBrightnessLevels); assertEquals(data, null); - data = BrightnessThrottlingData.create(unsortedLevels); + data = ThermalBrightnessThrottlingData.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() { + public void testThermalThrottlingUnsupported() { final BrightnessThrottler throttler = createThrottlerUnsupported(); assertFalse(throttler.deviceSupportsThrottling()); @@ -157,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()); @@ -212,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, @@ -221,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()); @@ -292,32 +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(); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.4f); // Set new (valid) data from device config - mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); + mDeviceConfigFake.setThermalBrightnessThrottlingData("123,1,critical,0.8"); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); - mDeviceConfigFake.setBrightnessThrottlingData( + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,1,critical,0.75;123,1,critical,0.99,id_2"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f); - mDeviceConfigFake.setBrightnessThrottlingData( + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.75f); + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,1,critical,0.8,default;123,1,critical,0.99,id_2"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.8f); } @Test public void testInvalidThrottlingStrings() throws Exception { @@ -327,45 +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.setBrightnessThrottlingData( + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,default,1,critical,0.75,1,critical,0.99"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); // Invalid level string and number string - mDeviceConfigFake.setBrightnessThrottlingData( + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,1,1,critical,0.75,id_2,1,critical,0.99"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + testThermalThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); // Invalid format - (two default ids for same display) - mDeviceConfigFake.setBrightnessThrottlingData( + mDeviceConfigFake.setThermalBrightnessThrottlingData( "123,1,critical,0.75,default;123,1,critical,0.99"); - testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f); + 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); @@ -388,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, @@ -398,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); @@ -466,12 +475,13 @@ public class BrightnessThrottlerTest { private BrightnessThrottler createThrottlerUnsupported() { return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, /* throttlingChangeCallback= */ () -> {}, /* uniqueDisplayId= */ null, - /* throttlingDataId= */ null, /* throttlingDataMap= */ new HashMap<>(1)); + /* thermalThrottlingDataId= */ null, + /* thermalThrottlingDataMap= */ new HashMap<>(1)); } - private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) { + private BrightnessThrottler createThrottlerSupported(ThermalBrightnessThrottlingData data) { assertNotNull(data); - HashMap<String, BrightnessThrottlingData> throttlingDataMap = new HashMap<>(1); + HashMap<String, ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(1); throttlingDataMap.put("default", data); return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(), () -> {}, "123", "default", throttlingDataMap); 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 ed0755949869..5837b21b89fd 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -187,72 +187,72 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(new int[]{-1, 10, 20, 30, 40}, mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux()); - List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel> + List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel> defaultThrottlingLevels = new ArrayList<>(); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f )); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f )); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f )); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f )); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f )); defaultThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f )); - DisplayDeviceConfig.BrightnessThrottlingData defaultThrottlingData = - new DisplayDeviceConfig.BrightnessThrottlingData(defaultThrottlingLevels); + DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData = + new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels); - List<DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel> + List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel> concurrentThrottlingLevels = new ArrayList<>(); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f )); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f )); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f )); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f )); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f )); concurrentThrottlingLevels.add( - new DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel( + new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel( DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f )); - DisplayDeviceConfig.BrightnessThrottlingData concurrentThrottlingData = - new DisplayDeviceConfig.BrightnessThrottlingData(concurrentThrottlingLevels); + DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData = + new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels); - HashMap<String, DisplayDeviceConfig.BrightnessThrottlingData> throttlingDataMap = + HashMap<String, DisplayDeviceConfig.ThermalBrightnessThrottlingData> throttlingDataMap = new HashMap<>(2); throttlingDataMap.put("default", defaultThrottlingData); throttlingDataMap.put("concurrent", concurrentThrottlingData); assertEquals(throttlingDataMap, - mDisplayDeviceConfig.getBrightnessThrottlingDataMapByThrottlingId()); + mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()); assertNotNull(mDisplayDeviceConfig.getHostUsiVersion()); assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2); @@ -351,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); @@ -369,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/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 550204b99323..4cfbb9520d5f 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -105,6 +105,7 @@ public class LocaleManagerServiceTest { mMockPackageManager = mock(PackageManager.class); mMockPackageMonitor = mock(PackageMonitor.class); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); // For unit tests, set the default installer info doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) .getInstallSourceInfo(anyString()); diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index da9de2562930..e20f1e7065d4 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -131,6 +131,7 @@ public class SystemAppUpdateTrackerTest { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); mStoragefile = new AtomicFile(new File( Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index 82bc6f6c5263..06fc01738967 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -87,6 +87,7 @@ public class UiServiceTestCase { Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser( any(), any(), any(), any(), any()); Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); + Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any(), anyInt()); Mockito.doNothing().when(mContext).unregisterReceiver(any()); } 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 1ba6ad7c9f34..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( @@ -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/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java index b1a9f081253c..34bb664c9598 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java @@ -96,8 +96,6 @@ public class RoleObserverTest extends UiServiceTestCase { private TestableNotificationManagerService mService; private NotificationManagerService.RoleObserver mRoleObserver; - private TestableContext mContext = spy(getContext()); - @Mock private PreferencesHelper mPreferencesHelper; @Mock diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index b8a21ec4c030..8f2b470908c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -83,6 +83,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; +import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME; import static com.android.server.wm.ActivityRecord.State.DESTROYED; import static com.android.server.wm.ActivityRecord.State.DESTROYING; import static com.android.server.wm.ActivityRecord.State.FINISHING; @@ -589,12 +590,18 @@ public class ActivityRecordTests extends WindowTestsBase { throw new IllegalStateException("Orientation in new config should be either" + "landscape or portrait."); } + + final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation(); + spyOn(displayRotation); + activity.setRequestedOrientation(requestedOrientation); final ActivityConfigurationChangeItem expected = ActivityConfigurationChangeItem.obtain(newConfig); verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()), eq(activity.token), eq(expected)); + + verify(displayRotation).onSetRequestedOrientation(); } @Test @@ -3682,6 +3689,23 @@ public class ActivityRecordTests extends WindowTestsBase { assertTrue(activity.inTransition()); } + /** + * Verifies the task is moved to back when back pressed if the root activity was originally + * started from Launcher. + */ + @Test + public void testMoveTaskToBackWhenStartedFromLauncher() { + final Task task = createTask(mDisplayContent); + final ActivityRecord ar = createActivityRecord(task); + task.realActivity = ar.mActivityComponent; + ar.intent.setAction(Intent.ACTION_MAIN); + ar.intent.addCategory(Intent.CATEGORY_LAUNCHER); + doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME)); + + mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */); + verify(task).moveTaskToBack(any()); + } + private ICompatCameraControlCallback getCompatCameraControlCallback() { return new ICompatCameraControlCallback.Stub() { @Override 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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index ba9f809e9a2a..7330411d1dd7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; @@ -1063,6 +1064,51 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); } + private void updateAllDisplayContentAndRotation(DisplayContent dc) { + // NB updateOrientation will not revert the user orientation until a settings change + // takes effect. + dc.updateOrientation(); + dc.onDisplayChanged(dc); + dc.mWmService.updateRotation(true /* alwaysSendConfiguration */, + false /* forceRelayout */); + waitUntilHandlersIdle(); + } + + @Test + public void testNoSensorRevert() { + final DisplayContent dc = mDisplayContent; + spyOn(dc); + doReturn(true).when(dc).getIgnoreOrientationRequest(); + final DisplayRotation dr = dc.getDisplayRotation(); + spyOn(dr); + doReturn(false).when(dr).useDefaultSettingsProvider(); + final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); + app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app); + + assertFalse(dc.getRotationReversionController().isAnyOverrideActive()); + dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED, + ROTATION_90); + updateAllDisplayContentAndRotation(dc); + assertEquals(ROTATION_90, dc.getDisplayRotation() + .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_90)); + + app.setOrientation(SCREEN_ORIENTATION_NOSENSOR); + updateAllDisplayContentAndRotation(dc); + assertTrue(dc.getRotationReversionController().isAnyOverrideActive()); + assertEquals(ROTATION_0, dc.getRotation()); + + app.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED); + updateAllDisplayContentAndRotation(dc); + assertFalse(dc.getRotationReversionController().isAnyOverrideActive()); + assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, + dc.getDisplayRotation().getUserRotationMode()); + assertEquals(ROTATION_90, dc.getDisplayRotation().getUserRotation()); + assertEquals(ROTATION_90, dc.getDisplayRotation() + .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_0)); + dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE, + ROTATION_0); + } + @Test public void testOnDescendantOrientationRequestChanged() { final DisplayContent dc = createNewDisplay(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index c2b3783b7311..a3117269eb01 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -365,6 +365,23 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test + public void testCameraDisconnected_revertRotationAndRefresh() throws Exception { + configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE); + // Open camera and test for compat treatment + mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + assertEquals(mDisplayRotationCompatPolicy.getOrientation(), + SCREEN_ORIENTATION_LANDSCAPE); + assertActivityRefreshRequested(/* refreshRequested */ true); + // Close camera and test for revert + mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1); + callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); + assertEquals(mDisplayRotationCompatPolicy.getOrientation(), + SCREEN_ORIENTATION_UNSPECIFIED); + assertActivityRefreshRequested(/* refreshRequested */ true); + } + + @Test public void testGetOrientation_cameraConnectionClosed_returnUnspecified() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 495f868b1b11..4b2d1071d113 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -70,6 +70,7 @@ import android.view.IRotationWatcher; import android.view.Surface; import android.view.WindowManager; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.util.test.FakeSettingsProvider; @@ -114,6 +115,7 @@ public class DisplayRotationTests { private static WindowManagerService sMockWm; private DisplayContent mMockDisplayContent; + private DisplayRotationReversionController mMockDisplayRotationReversionController; private DisplayPolicy mMockDisplayPolicy; private DisplayAddress mMockDisplayAddress; private Context mMockContext; @@ -140,6 +142,8 @@ public class DisplayRotationTests { private DeviceStateController mDeviceStateController; private TestDisplayRotation mTarget; + @Nullable + private DisplayRotationImmersiveAppCompatPolicy mDisplayRotationImmersiveAppCompatPolicyMock; @BeforeClass public static void setUpOnce() { @@ -165,7 +169,7 @@ public class DisplayRotationTests { LocalServices.removeServiceForTest(StatusBarManagerInternal.class); mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal); - + mDisplayRotationImmersiveAppCompatPolicyMock = null; mBuilder = new DisplayRotationBuilder(); } @@ -578,6 +582,38 @@ public class DisplayRotationTests { } @Test + public void testNotifiesChoiceWhenSensorUpdates_immersiveApp() throws Exception { + mDisplayRotationImmersiveAppCompatPolicyMock = mock( + DisplayRotationImmersiveAppCompatPolicy.class); + when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced( + Surface.ROTATION_90)).thenReturn(true); + + mBuilder.build(); + configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false); + + thawRotation(); + + enableOrientationSensor(); + + mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90)); + assertTrue(waitForUiHandler()); + + verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true); + + // An imaginary ActivityRecord.setRequestedOrientation call disables immersive mode: + when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced( + Surface.ROTATION_90)).thenReturn(false); + + // And then ActivityRecord.setRequestedOrientation calls onSetRequestedOrientation. + mTarget.onSetRequestedOrientation(); + + // onSetRequestedOrientation should lead to a second call to + // mOrientationListener.onProposedRotationChanged + // but now, instead of notifying mMockStatusBarManagerInternal, it calls updateRotation: + verify(sMockWm).updateRotation(false, false); + } + + @Test public void testAllowAllRotations_allowsUpsideDownSuggestion() throws Exception { mBuilder.build(); @@ -1374,6 +1410,10 @@ public class DisplayRotationTests { when(mMockContext.getResources().getBoolean( com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride)) .thenReturn(mSupportHalfFoldAutoRotateOverride); + mMockDisplayRotationReversionController = + mock(DisplayRotationReversionController.class); + when(mMockDisplayContent.getRotationReversionController()) + .thenReturn(mMockDisplayRotationReversionController); mMockResolver = mock(ContentResolver.class); when(mMockContext.getContentResolver()).thenReturn(mMockResolver); @@ -1404,7 +1444,7 @@ public class DisplayRotationTests { } } - private static class TestDisplayRotation extends DisplayRotation { + private class TestDisplayRotation extends DisplayRotation { IntConsumer mProposedRotationCallback; TestDisplayRotation(DisplayContent dc, DisplayAddress address, DisplayPolicy policy, @@ -1417,7 +1457,7 @@ public class DisplayRotationTests { @Override DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy( WindowManagerService service, DisplayContent displayContent) { - return null; + return mDisplayRotationImmersiveAppCompatPolicyMock; } @Override 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/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 e96d1abf9ced..de943d240084 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -428,20 +429,24 @@ public class SizeCompatTests extends WindowTestsBase { .setLaunchedFromUid(mActivity.getUid()) .build(); doReturn(false).when(translucentActivity).fillsParent(); - WindowConfiguration translucentWinConf = translucentActivity.getWindowConfiguration(); - translucentActivity.setActivityType(ACTIVITY_TYPE_STANDARD); - translucentActivity.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - translucentActivity.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - translucentActivity.setAlwaysOnTop(true); + final Configuration requestedConfig = + translucentActivity.getRequestedOverrideConfiguration(); + final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration; + translucentWinConf.setActivityType(ACTIVITY_TYPE_STANDARD); + translucentWinConf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + translucentWinConf.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + translucentWinConf.setAlwaysOnTop(true); + translucentActivity.onRequestedOverrideConfigurationChanged(requestedConfig); mTask.addChild(translucentActivity); - // We check the WIndowConfiguration properties - translucentWinConf = translucentActivity.getWindowConfiguration(); + // The original override of WindowConfiguration should keep. assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType()); assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode()); assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode()); assertTrue(translucentWinConf.isAlwaysOnTop()); + // Unless display is going to be rotated, it should always inherit from parent. + assertEquals(ROTATION_UNDEFINED, translucentWinConf.getDisplayRotation()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java index 8e91ca28fcf1..77efc4b0d561 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java @@ -42,6 +42,8 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; +import com.android.server.testutils.TestHandler; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -371,6 +373,49 @@ public class SyncEngineTests extends WindowTestsBase { mAppWindow.removeImmediately(); } + @Test + public void testQueueSyncSet() { + final TestHandler testHandler = new TestHandler(null); + TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */); + TestWindowContainer mockWC2 = new TestWindowContainer(mWm, true /* waiter */); + + final BLASTSyncEngine bse = createTestBLASTSyncEngine(testHandler); + + BLASTSyncEngine.TransactionReadyListener listener = mock( + BLASTSyncEngine.TransactionReadyListener.class); + + int id = startSyncSet(bse, listener); + bse.addToSyncSet(id, mockWC); + bse.setReady(id); + bse.onSurfacePlacement(); + verify(listener, times(0)).onTransactionReady(eq(id), notNull()); + + final int[] nextId = new int[]{-1}; + bse.queueSyncSet( + () -> nextId[0] = startSyncSet(bse, listener), + () -> { + bse.setReady(nextId[0]); + bse.addToSyncSet(nextId[0], mockWC2); + }); + + // Make sure it is queued + assertEquals(-1, nextId[0]); + + // Finish the original sync and see that we've started a new sync-set immediately but + // that the readiness was posted. + mockWC.onSyncFinishedDrawing(); + verify(mWm.mWindowPlacerLocked).requestTraversal(); + bse.onSurfacePlacement(); + verify(listener, times(1)).onTransactionReady(eq(id), notNull()); + + assertTrue(nextId[0] != -1); + assertFalse(bse.isReady(nextId[0])); + + // now make sure the applySync callback was posted. + testHandler.flush(); + assertTrue(bse.isReady(nextId[0])); + } + static int startSyncSet(BLASTSyncEngine engine, BLASTSyncEngine.TransactionReadyListener listener) { return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test"); diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 90506d4f8651..1e2fdec07d00 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1404,8 +1404,6 @@ public class TransitionTests extends WindowTestsBase { // We are now going to simulate closing task1 to return back to (open) task2. final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE); - closeTransition.collectExistenceChange(task1); - closeTransition.collectExistenceChange(activity1); closeTransition.collectExistenceChange(task2); closeTransition.collectExistenceChange(activity2); closeTransition.setTransientLaunch(activity2, task1); @@ -1416,7 +1414,7 @@ public class TransitionTests extends WindowTestsBase { assertNotNull(activity1ChangeInfo); assertTrue(activity1ChangeInfo.hasChanged()); // No need to wait for the activity in transient hide task. - assertTrue(activity1.isSyncFinished()); + assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState); activity1.setVisibleRequested(false); activity2.setVisibleRequested(true); 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/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 7e3ec55f262a..f85cdf0b5035 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -77,6 +77,7 @@ import android.hardware.display.DisplayManager; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -886,7 +887,11 @@ class WindowTestsBase extends SystemServiceTestsBase { } BLASTSyncEngine createTestBLASTSyncEngine() { - return new BLASTSyncEngine(mWm) { + return createTestBLASTSyncEngine(mWm.mH); + } + + BLASTSyncEngine createTestBLASTSyncEngine(Handler handler) { + return new BLASTSyncEngine(mWm, handler) { @Override void scheduleTimeout(SyncGroup s, long timeoutMs) { // Disable timeout. diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java index f3ef834168b5..52ff90f38113 100644 --- a/telecomm/java/android/telecom/CallAttributes.java +++ b/telecomm/java/android/telecom/CallAttributes.java @@ -59,7 +59,10 @@ public final class CallAttributes implements Parcelable { public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities"; /** @hide **/ - public static final String CALLER_PID = "CallerPid"; + public static final String CALLER_PID_KEY = "CallerPid"; + + /** @hide **/ + public static final String CALLER_UID_KEY = "CallerUid"; private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle, @NonNull CharSequence displayName, diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index cbdf38ae95d4..ee9d6c1a2448 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2989,4 +2989,14 @@ interface ITelephony { * {@code false} otherwise. */ boolean setSatelliteServicePackageName(in String servicePackageName); + + /** + * This API can be used by only CTS to update the timeout duration in milliseconds that + * satellite should stay at listening mode to wait for the next incoming page before disabling + * listening mode. + * + * @param timeoutMillis The timeout duration in millisecond. + * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise. + */ + boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis); } diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java index cfebf3462b57..4299e0d616fb 100644 --- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java +++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java @@ -87,7 +87,7 @@ public final class DynamicCodeLoggerIntegrationTests { // avoid flakiness we run these tests multiple times, allowing progressively longer between // code loading and checking the logs on each try.) private static final int AUDIT_LOG_RETRIES = 10; - private static final int RETRY_DELAY_MS = 2_000; + private static final int RETRY_DELAY_MS = 500; private static Context sContext; private static int sMyUid; @@ -253,7 +253,7 @@ public final class DynamicCodeLoggerIntegrationTests { "/DynamicCodeLoggerNativeExecutable", privateCopyFile); EventLog.writeEvent(EventLog.getTagCode("auditd"), - "type=1400 avc: granted { execute_no_trans } " + "type=1400 avc: granted { execute_no_trans } " + "path=\"" + privateCopyFile + "\" " + "scontext=u:r:untrusted_app: " + "tcontext=u:object_r:app_data_file: " diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 314b9e4a853b..f389e13a6884 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -260,7 +260,7 @@ fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: ICompon snapshotLayers .mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion } .toTypedArray() - val snapshotRegion = RegionSubject(visibleAreas, this, timestamp) + val snapshotRegion = RegionSubject(visibleAreas, timestamp) val appVisibleRegion = it.visibleRegion(component) if (snapshotRegion.region.isNotEmpty) { snapshotRegion.coversExactly(appVisibleRegion.region) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt index 6066d2e74209..34fa921f5777 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -88,7 +88,7 @@ open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(fli imeSnapshotLayers .mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion } .toTypedArray() - val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp) + val imeVisibleRegion = RegionSubject(visibleAreas, timestamp) val appVisibleRegion = it.visibleRegion(imeTestApp) if (imeVisibleRegion.region.isNotEmpty) { imeVisibleRegion.coversAtMost(appVisibleRegion.region) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index 2fff00133eb8..d5208e060883 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -73,7 +73,16 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f @Test @IwTest(focusArea = "ime") override fun cujCompleted() { - super.cujCompleted() + runAndIgnoreAssumptionViolation { entireScreenCovered() } + runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() } + runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() } + runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() } + runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() } + runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() } imeLayerBecomesInvisible() imeWindowBecomesInvisible() } 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", ], } diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml index d2653d0de0d4..f20dd424c617 100644 --- a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml +++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml @@ -18,6 +18,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:startColor="#000000" - android:endColor="#181818" + android:endColor="#222222" android:angle="0"/> </shape>
\ No newline at end of file diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index e5ef62b16dfd..feef0497c152 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -35,6 +35,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; import com.android.internal.R; @@ -173,10 +174,15 @@ public class SharedConnectivityManager { R.string.config_sharedConnectivityServicePackage); String serviceIntentAction = resources.getString( R.string.config_sharedConnectivityServiceIntentAction); + if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) { + Log.e(TAG, "To support shared connectivity service on this device, the" + + " service's package name and intent action strings must not be empty"); + return null; + } return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); } catch (Resources.NotFoundException e) { Log.e(TAG, "To support shared connectivity service on this device, the service's" - + " package name and intent action string must be defined"); + + " package name and intent action strings must be defined"); } return null; } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java index b585bd5cfd7b..a03a6c2fc673 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -105,6 +105,13 @@ public class SharedConnectivityManagerTest { } @Test + public void resourceStringsAreEmpty_createShouldReturnNull() { + when(mResources.getString(anyInt())).thenReturn(""); + + assertThat(SharedConnectivityManager.create(mContext)).isNull(); + } + + @Test public void bindingToServiceOnFirstCallbackRegistration() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.registerCallback(mExecutor, mClientCallback); |